]> git.xonotic.org Git - xonotic/netradiant.git/blobdiff - radiant/gtkmisc.cpp
Another Windows file dialog change in Rambetter-temp-fixes branch.
[xonotic/netradiant.git] / radiant / gtkmisc.cpp
index bf1731996ffa88191036cfa50d68a19a19e14f81..f398604b8cf18fbe3280196245f3ef7ffb55913e 100644 (file)
@@ -2,30 +2,30 @@
 Copyright (c) 2001, Loki software, inc.
 All rights reserved.
 
-Redistribution and use in source and binary forms, with or without modification, 
+Redistribution and use in source and binary forms, with or without modification,
 are permitted provided that the following conditions are met:
 
-Redistributions of source code must retain the above copyright notice, this list 
+Redistributions of source code must retain the above copyright notice, this list
 of conditions and the following disclaimer.
 
 Redistributions in binary form must reproduce the above copyright notice, this
 list of conditions and the following disclaimer in the documentation and/or
 other materials provided with the distribution.
 
-Neither the name of Loki software nor the names of its contributors may be used 
-to endorse or promote products derived from this software without specific prior 
-written permission. 
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' 
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY 
-DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+Neither the name of Loki software nor the names of its contributors may be used
+to endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
 //
@@ -33,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 //
 
 #include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
 
 #if defined (__linux__) || defined (__APPLE__)
 #include <unistd.h>
@@ -40,7 +41,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <gtk/gtk.h>
 
-
 #ifdef _WIN32
 #include <gdk/gdkwin32.h>
 #define WIN32_LEAN_AND_MEAN
@@ -81,7 +81,6 @@ void save_window_pos (GtkWidget *wnd, window_position_t& pos)
 #ifdef _WIN32
 void win32_get_window_pos(GtkWidget *widget, gint *x, gint *y)
 {
-  // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=913
   if ( g_PrefsDlg.m_bStartOnPrimMon ) {
     RECT rc;
     POINT point;
@@ -106,7 +105,7 @@ void win32_get_window_pos(GtkWidget *widget, gint *x, gint *y)
   }
 #ifdef DBG_WINDOWPOS
   Sys_Printf("win32_get_window_pos %p %d,%d\n",widget,*x,*y);
-#endif 
+#endif
 }
 #endif
 
@@ -318,7 +317,7 @@ unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *
                return NULL;
          }
 
-         rc = fread(&g, 1, 1, fp); 
+         rc = fread(&g, 1, 1, fp);
          m_bytesRead++;
          if (rc!=1)
          {
@@ -327,7 +326,7 @@ unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *
                return NULL;
          }
 
-         rc = fread(&r, 1, 1, fp); 
+         rc = fread(&r, 1, 1, fp);
          m_bytesRead++;
          if (rc != 1)
          {
@@ -336,7 +335,7 @@ unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *
                return NULL;
          }
 
-         rc = fread(&dummy, 1, 1, fp); 
+         rc = fread(&dummy, 1, 1, fp);
          m_bytesRead++;
          if (rc != 1)
          {
@@ -372,7 +371,7 @@ unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *
   imagebits = (unsigned char *)malloc(w * h * 3);
   long row_size = w * 3;
 
-  if (imagebits != NULL) 
+  if (imagebits != NULL)
   {
          *width = w;
          *height = h;
@@ -386,7 +385,7 @@ unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *
            for (row = bmHeight - 1; row >= 0; row--)
            {
                    // which row are we working on?
-                   rowOffset = (long unsigned)row * row_size;                                                
+                   rowOffset = (long unsigned)row * row_size;
 
                    if (bmBitsPixel == 24)
                    {
@@ -477,13 +476,13 @@ unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *
            unsigned char c, c1 = 0, *pp;
            row = 0;
            pp = outbuf + (bmHeight - 1) * bmWidth * 3;
-    
+
            if (bmBitsPixel == 8)
            {
                    while (row < bmHeight)
                    {
                      c = getc(fp);
-       
+
                      if (c)
                      {
                              // encoded mode
@@ -524,7 +523,7 @@ unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *
                                        *pp = colormap[c1].rgbGreen; pp++;
                                        *pp = colormap[c1].rgbBlue; pp++;
                                }
-        
+
                                if (c & 1)
                                      getc(fp); // odd length run: read an extra pad byte
                              }
@@ -536,7 +535,7 @@ unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *
                    while (row < bmHeight)
                    {
                      c = getc(fp);
-      
+
                      if (c)
                      {
                              // encoded mode
@@ -552,7 +551,7 @@ unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *
                      {
                              // c==0x00,  escape codes
                              c = getc(fp);
-        
+
                              if (c == 0x00) // end of line
                              {
                                row++;
@@ -579,7 +578,7 @@ unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *
                                        *pp = colormap[(i&1) ? (c1 & 0x0f) : ((c1>>4)&0x0f)].rgbGreen; pp++;
                                        *pp = colormap[(i&1) ? (c1 & 0x0f) : ((c1>>4)&0x0f)].rgbBlue; pp++;
                                }
-        
+
                                if (((c & 3) == 1) || ((c & 3) == 2))
                                      getc(fp); // odd length run: read an extra pad byte
             }
@@ -589,7 +588,7 @@ unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *
          }
          if (colormap)
            delete [] colormap;
-    
+
          fclose(fp);
   }
   return imagebits;
@@ -686,7 +685,7 @@ void load_pixmap (const char* filename, GtkWidget* widget, GdkPixmap **gdkpixmap
   if (*gdkpixmap == NULL)
   {
     printf("gdkpixmap was null\n");
-    char *dummy[] = { "1 1 1 1", "  c None", " " };
+    gchar *dummy[] = { "1 1 1 1", "  c None", " " };
     printf("calling gdk_pixmap_create_from_xpm_d\n");
     *gdkpixmap = gdk_pixmap_create_from_xpm_d (gdk_get_default_root_window(), mask, NULL, dummy);
   }
@@ -715,17 +714,17 @@ bool WINAPI load_plugin_bitmap (const char* filename, void **gdkpixmap, void **m
 
     if (*gdkpixmap == NULL)
     {
-      
+
       // look in core modules
       str = g_strAppPath;
       str += g_strModulesDir;
       str += "bitmaps/";
       str += filename;
       bmp_to_pixmap (str.GetBuffer (), (GdkPixmap **)gdkpixmap, (GdkBitmap **)mask);
-      
+
       if (*gdkpixmap == NULL)
       {
-        char *dummy[] = { "1 1 1 1", "  c None", " " };
+        gchar *dummy[] = { "1 1 1 1", "  c None", " " };
         *gdkpixmap = gdk_pixmap_create_from_xpm_d (gdk_get_default_root_window(), (GdkBitmap **)mask, NULL, dummy);
         return false;
       }
@@ -735,7 +734,7 @@ bool WINAPI load_plugin_bitmap (const char* filename, void **gdkpixmap, void **m
 }
 
 // Load a xpm file and return a pixmap widget.
-GtkWidget* new_pixmap (GtkWidget* widget, char* filename)
+GtkWidget* new_pixmap (GtkWidget* widget, const char* filename)
 {
   GdkPixmap *gdkpixmap;
   GdkBitmap *mask;
@@ -748,7 +747,7 @@ GtkWidget* new_pixmap (GtkWidget* widget, char* filename)
   gdk_drawable_unref (mask);
 
   return pixmap;
-} 
+}
 
 // =============================================================================
 // Menu stuff
@@ -770,8 +769,8 @@ GtkWidget* menu_tearoff (GtkWidget *menu)
   gtk_widget_show (menu_item);
   return menu_item;
 }
-GtkWidget* create_sub_menu_with_mnemonic (GtkWidget *bar, gchar *mnemonic)
+
+GtkWidget* create_sub_menu_with_mnemonic (GtkWidget *bar, const gchar *mnemonic)
 {
   GtkWidget *item, *sub_menu;
 
@@ -787,7 +786,7 @@ GtkWidget* create_sub_menu_with_mnemonic (GtkWidget *bar, gchar *mnemonic)
 
 extern void AddMenuItem (GtkWidget* menu, unsigned int id);
 
-GtkWidget* create_menu_item_with_mnemonic (GtkWidget *menu, gchar *mnemonic, GtkSignalFunc func, int id)
+GtkWidget* create_menu_item_with_mnemonic (GtkWidget *menu, const gchar *mnemonic, GtkSignalFunc func, int id)
 {
   GtkWidget *item;
 
@@ -801,7 +800,7 @@ GtkWidget* create_menu_item_with_mnemonic (GtkWidget *menu, gchar *mnemonic, Gtk
   return item;
 }
 
-GtkWidget* create_check_menu_item_with_mnemonic (GtkWidget *menu, gchar *mnemonic, GtkSignalFunc func, int id, gboolean active)
+GtkWidget* create_check_menu_item_with_mnemonic (GtkWidget *menu, const gchar *mnemonic, GtkSignalFunc func, int id, gboolean active)
 {
   GtkWidget *item;
 
@@ -816,7 +815,7 @@ GtkWidget* create_check_menu_item_with_mnemonic (GtkWidget *menu, gchar *mnemoni
   return item;
 }
 
-GtkWidget* create_radio_menu_item_with_mnemonic (GtkWidget *menu, GtkWidget *last, gchar *mnemonic, GtkSignalFunc func, int id, gboolean state)
+GtkWidget* create_radio_menu_item_with_mnemonic (GtkWidget *menu, GtkWidget *last, const gchar *mnemonic, GtkSignalFunc func, int id, gboolean state)
 {
   GtkWidget *item;
   GSList *group = (GSList*)NULL;
@@ -860,7 +859,7 @@ void dialog_button_callback( GtkWidget *widget, gpointer data ) {
   ret = (int*)g_object_get_data( G_OBJECT( parent ), "ret" );
 
   *loop = 0;
-  *ret = (int)data;
+  *ret = GPOINTER_TO_INT (data);
 }
 
 gint dialog_delete_callback (GtkWidget *widget, GdkEvent* event, gpointer data)
@@ -922,10 +921,10 @@ int WINAPI gtk_MessageBox (void *parent, const char* lpText, const char* lpCapti
   hbox = gtk_hbox_new (FALSE, 10);
   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2);
   gtk_widget_show (hbox);
+
   if (mode == MB_OK)
   {
-    w = gtk_button_new_with_label ("Ok");
+    w = gtk_button_new_with_label (_("Ok"));
     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
     gtk_signal_connect (GTK_OBJECT (w), "clicked",
                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK));
@@ -938,7 +937,7 @@ int WINAPI gtk_MessageBox (void *parent, const char* lpText, const char* lpCapti
   }
   else if (mode ==  MB_OKCANCEL)
   {
-    w = gtk_button_new_with_label ("Ok");
+    w = gtk_button_new_with_label (_("Ok"));
     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
     gtk_signal_connect (GTK_OBJECT (w), "clicked",
                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK));
@@ -947,7 +946,7 @@ int WINAPI gtk_MessageBox (void *parent, const char* lpText, const char* lpCapti
     gtk_widget_grab_default (w);
     gtk_widget_show (w);
 
-    w = gtk_button_new_with_label ("Cancel");
+    w = gtk_button_new_with_label (_("Cancel"));
     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
     gtk_signal_connect (GTK_OBJECT (w), "clicked",
                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL));
@@ -957,21 +956,21 @@ int WINAPI gtk_MessageBox (void *parent, const char* lpText, const char* lpCapti
   }
   else if (mode == MB_YESNOCANCEL)
   {
-    w = gtk_button_new_with_label ("Yes");
+    w = gtk_button_new_with_label (_("Yes"));
     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
     gtk_signal_connect (GTK_OBJECT (w), "clicked",
                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDYES));
     GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
     gtk_widget_grab_default (w);
     gtk_widget_show (w);
-    w = gtk_button_new_with_label ("No");
+
+    w = gtk_button_new_with_label (_("No"));
     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
     gtk_signal_connect (GTK_OBJECT (w), "clicked",
                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDNO));
     gtk_widget_show (w);
-    w = gtk_button_new_with_label ("Cancel");
+
+    w = gtk_button_new_with_label (_("Cancel"));
     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
     gtk_signal_connect (GTK_OBJECT (w), "clicked",
                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL));
@@ -980,15 +979,15 @@ int WINAPI gtk_MessageBox (void *parent, const char* lpText, const char* lpCapti
   }
   else /* if (mode == MB_YESNO) */
   {
-    w = gtk_button_new_with_label ("Yes");
+    w = gtk_button_new_with_label (_("Yes"));
     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
     gtk_signal_connect (GTK_OBJECT (w), "clicked",
                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDYES));
     GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
     gtk_widget_grab_default (w);
     gtk_widget_show (w);
-    w = gtk_button_new_with_label ("No");
+
+    w = gtk_button_new_with_label (_("No"));
     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
     gtk_signal_connect (GTK_OBJECT (w), "clicked",
                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDNO));
@@ -998,7 +997,7 @@ int WINAPI gtk_MessageBox (void *parent, const char* lpText, const char* lpCapti
 
   if (URL)
   {
-    w = gtk_button_new_with_label ("Go to URL");
+    w = gtk_button_new_with_label (_("Go to URL"));
     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
     gtk_signal_connect (GTK_OBJECT (w), "clicked",
                         GTK_SIGNAL_FUNC (dialog_url_callback), NULL);
@@ -1037,26 +1036,26 @@ static void file_sel_callback (GtkWidget *widget, gpointer data)
   parent = gtk_widget_get_toplevel (widget);
   loop = (int*)g_object_get_data (G_OBJECT (parent), "loop");
   success = (bool*)g_object_get_data (G_OBJECT (parent), "success");
-  
-  if ((int)data == IDOK)
+
+  if (GPOINTER_TO_INT (data) == IDOK)
     *success = true;
 
 #ifdef FILEDLG_DBG
   else
     Sys_Printf("file_sel_callback != IDOK\n");
 #endif
-  
+
   *loop = 0;
 }
 
 #ifdef _WIN32
 #include <commdlg.h>
-static OPENFILENAME ofn;       /* common dialog box structure   */ 
-static char szDirName[MAX_PATH];    /* directory string              */ 
-static char szFile[MAX_PATH];       /* filename string               */ 
-static char szFileTitle[MAX_PATH];  /* file title string             */ 
-static int i, cbString;        /* integer count variables       */ 
-static HANDLE hf;              /* file handle                   */ 
+static OPENFILENAME ofn;       /* common dialog box structure   */
+static char szDirName[MAX_PATH];    /* directory string              */
+static char szFile[MAX_PATH];       /* filename string               */
+static char szFileTitle[MAX_PATH];  /* file title string             */
+static int i, cbString;        /* integer count variables       */
+static HANDLE hf;              /* file handle                   */
 #else
 static char szFile[QER_MAX_NAMELEN];
 #endif
@@ -1107,7 +1106,7 @@ public:
     ConstructGTKMasks();
     ConstructWin32Filters();
   }
+
   filetype_t GetTypeForWin32Filter(const char *filter) const
   {
     for(int i=0; i<m_nTypes; i++)
@@ -1124,6 +1123,18 @@ public:
     return filetype_t();
   }
 
+  int GetNumTypes()
+  {
+    return m_nTypes;
+  }
+
+  filetype_t GetTypeForIndex(int index) const // Zero-based index.
+  {
+    if (index >= 0 && index < m_nTypes)
+      return filetype_t(m_pTypes[index].m_name.c_str(), m_pTypes[index].m_pattern.c_str());
+    return filetype_t();
+  }
+
   char *m_strWin32Filters;
   char **m_pstrGTKMasks;
 private:
@@ -1169,7 +1180,7 @@ private:
         delete[] *p;
     delete[] m_pstrGTKMasks;
   }
-  
+
   void ConstructGTKMasks()
   {
     const char *r;
@@ -1197,8 +1208,45 @@ private:
 
 };
 
-const char* file_dialog (void *parent, gboolean open, const char* title, const char* path, const char* pattern)
+#ifdef _WIN32
+
+static int in_file_dialog = 0;
+
+typedef struct {
+  gboolean open;
+  OPENFILENAME *ofn;
+  BOOL dlgRtnVal;
+  int done;
+} win32_native_file_dialog_comms_t;
+
+DWORD WINAPI win32_native_file_dialog_thread_func(LPVOID lpParam)
 {
+  win32_native_file_dialog_comms_t *fileDialogComms;
+  fileDialogComms = (win32_native_file_dialog_comms_t *) lpParam;
+  if (fileDialogComms->open) {
+    fileDialogComms->dlgRtnVal = GetOpenFileName(fileDialogComms->ofn);
+  }
+  else {
+    fileDialogComms->dlgRtnVal = GetSaveFileName(fileDialogComms->ofn);
+  }
+  fileDialogComms->done = -1; // No need to synchronize around lock.
+  return 0;
+}
+
+#endif
+
+/**
+ * @param[in] baseSubDir should have a trailing slash if not @c NULL
+ */
+const char* file_dialog (void *parent, gboolean open, const char* title, const char* path, const char* pattern, const char *baseSubDir)
+{
+
+#ifdef _WIN32
+  HANDLE fileDialogThreadHandle;
+  win32_native_file_dialog_comms_t fileDialogComms;
+  int dialogDone;
+#endif
+
   // Gtk dialog
   GtkWidget* file_sel;
   int loop = 1;
@@ -1225,33 +1273,35 @@ const char* file_dialog (void *parent, gboolean open, const char* title, const c
 #endif
 
 #ifdef _WIN32
-  // win32 dialog stores the selected "save as type" extension in the second null-terminated string
-  char customfilter[FILEDLG_CUSTOM_FILTER_LENGTH];
-
   if (g_PrefsDlg.m_bNativeGUI)
   {
 #ifdef FILEDLG_DBG
     Sys_Printf("Doing win32 file dialog...");
 #endif
     // do that the native way
-    /* Place the terminating null character in the szFile. */  
-    szFile[0] = '\0';
-    customfilter[0] = customfilter[1] = customfilter[2] = '\0';
-    
-    /* Set the members of the OPENFILENAME structure. */     
-    ofn.lStructSize = sizeof(OPENFILENAME); 
+
+    if (in_file_dialog) return NULL; // Avoid recursive entry.
+    in_file_dialog = 1;
+    /* Set the members of the OPENFILENAME structure. */
+    // See http://msdn.microsoft.com/en-us/library/ms646839%28v=vs.85%29.aspx .
+    memset(&ofn, 0, sizeof(ofn));
+    ofn.lStructSize = sizeof(ofn);
     ofn.hwndOwner = (HWND)GDK_WINDOW_HWND (g_pParentWnd->m_pWidget->window);
+    ofn.nFilterIndex = 1; // The index is 1-based, not 0-based.  This basically says,
+                          // "select the first filter as default".
     if (pattern)
     {
-      ofn.nFilterIndex = 0;
       ofn.lpstrFilter = typelist.m_strWin32Filters;
     }
-    else ofn.nFilterIndex = 1;
-    ofn.lpstrCustomFilter = customfilter;
-    ofn.nMaxCustFilter = sizeof(customfilter);
+    else
+    {
+      // TODO: Would be a bit cleaner if we could extract this string from
+      // GetFileTypeRegistry() instead of hardcoding it here.
+      ofn.lpstrFilter = "all files\0*.*\0"; // Second '\0' will be added to end of string.
+    }
+    szFile[0] = '\0';
     ofn.lpstrFile = szFile;
-    ofn.nMaxFile = sizeof(szFile); 
-    ofn.lpstrFileTitle = NULL; // we don't need to get the name of the file
+    ofn.nMaxFile = sizeof(szFile);
     if(path)
     {
       // szDirName: Radiant uses unix convention for paths internally
@@ -1263,26 +1313,47 @@ const char* file_dialog (void *parent, gboolean open, const char* title, const c
       *w = '\0';
       ofn.lpstrInitialDir = szDirName;
     }
-    else ofn.lpstrInitialDir = NULL;
     ofn.lpstrTitle = title;
-    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; 
-
-    /* Display the Open dialog box. */
-    // it's open or close depending on 'open' parameter
-    if (open)
-    {
-      if (!GetOpenFileName(&ofn))
-        return NULL;   // canceled
+    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
+
+    memset(&fileDialogComms, 0, sizeof(fileDialogComms));
+    fileDialogComms.open = open;
+    fileDialogComms.ofn = &ofn;
+
+    fileDialogThreadHandle =
+      CreateThread(NULL, // lpThreadAttributes
+                   0, // dwStackSize, default stack size
+                   win32_native_file_dialog_thread_func, // lpStartAddress, funcion to call
+                   &fileDialogComms, // lpParameter, argument to pass to function
+                   0, // dwCreationFlags
+                   NULL); // lpThreadId
+
+    dialogDone = 0;
+    while (1) {
+      // Avoid blocking indefinitely.  Another thread will set fileDialogComms->done to nonzero;
+      // we don't want to be in an indefinite blocked state when this happens.  We want to break
+      // out of here eventually.
+      while (gtk_events_pending()) {
+        gtk_main_iteration();
+      }
+      if (dialogDone) break;
+      if (fileDialogComms.done) dialogDone = 1; // One more loop of gtk_main_iteration() to get things in sync.
+      // Avoid tight infinte loop, add a small amount of sleep.
+      Sleep(10);
     }
-    else
-    {
-      if (!GetSaveFileName(&ofn))
-        return NULL;   // canceled
+    // Make absolutely sure that the thread is finished before we call CloseHandle().
+    WaitForSingleObject(fileDialogThreadHandle, INFINITE);
+    CloseHandle(fileDialogThreadHandle);
+
+    in_file_dialog = 0;
+    
+    if (!fileDialogComms.dlgRtnVal) {
+      return NULL; // Cancelled.
     }
 
     if(pattern != NULL)
-      type = typelist.GetTypeForWin32Filter(customfilter+1);
-    
+      type = typelist.GetTypeForIndex(ofn.nFilterIndex - 1);
+
 #ifdef FILEDLG_DBG
     Sys_Printf("Done.\n");
 #endif
@@ -1290,24 +1361,24 @@ const char* file_dialog (void *parent, gboolean open, const char* title, const c
   else
   {
 #endif
+       char buf[PATH_MAX];
     // do that the Gtk way
     if (title == NULL)
-      title = open ? "Open File" : "Save File";
-    
+      title = open ? _("Open File") : _("Save File");
+
 #ifdef FILEDLG_DBG
     Sys_Printf("Doing Gtk file dialog:\nBuilding new_path..");
 #endif
     // we expect an actual path below, if the path is NULL we might crash
     if (!path || path[0] == '\0')
     {
-#ifdef _WIN32
-      path = "C:\\";
-#elif defined (__linux__) || defined (__APPLE__)
-      path = "/";
-#else
-      path = "/";
-#endif
-    }
+               strcpy(buf, g_pGameDescription->mEnginePath.GetBuffer());
+               strcat(buf, g_pGameDescription->mBaseGame.GetBuffer());
+               strcat(buf, "/");
+               if (baseSubDir)
+                       strcat(buf, baseSubDir);
+               path = buf;
+       }
 
     // alloc new path with extra char for dir separator
     new_path = new char[strlen(path)+1+1];
@@ -1320,15 +1391,13 @@ const char* file_dialog (void *parent, gboolean open, const char* title, const c
     *w = '\0';
 
 #ifdef FILEDLG_DBG
-    Sys_Printf("Done.\n");
-    Sys_Printf("Calling gtk_file_selection_new with title: %s...", title);
+       Sys_Printf("Done.\n");
+       Sys_Printf("Calling gtk_file_selection_new with title: %s...", title);
 #endif
-
     file_sel = gtk_file_selection_new (title);
-
 #ifdef FILEDLG_DBG
-    Sys_Printf("Done.\n");
-    Sys_Printf("Set the masks...");
+       Sys_Printf("Done.\n");
+       Sys_Printf("Set the masks...");
 #endif
 
 #if 0 //!\todo Add masks to GtkFileSelection in gtk-2.0
@@ -1351,10 +1420,10 @@ const char* file_dialog (void *parent, gboolean open, const char* title, const c
     gtk_signal_connect (GTK_OBJECT (file_sel), "delete_event",
       GTK_SIGNAL_FUNC (dialog_delete_callback), NULL);
     gtk_file_selection_hide_fileop_buttons (GTK_FILE_SELECTION (file_sel));
-    
+
     if (parent != NULL)
       gtk_window_set_transient_for (GTK_WINDOW (file_sel), GTK_WINDOW (parent));
-    
+
 #ifdef FILEDLG_DBG
     Sys_Printf("set_data...");
 #endif
@@ -1364,7 +1433,7 @@ const char* file_dialog (void *parent, gboolean open, const char* title, const c
 #ifdef FILEDLG_DBG
     Sys_Printf("Done.\n");
 #endif
-    
+
     if (!open)
     {
 #ifdef FILEDLG_DBG
@@ -1375,11 +1444,11 @@ const char* file_dialog (void *parent, gboolean open, const char* title, const c
       Sys_Printf("Done.\n");
 #endif
     }
-    
+
     if (new_path != NULL)
     {
 #ifdef FILEDLG_DBG
-      Sys_Printf("gtk_file_selection_set_filename... %p", file_sel); 
+      Sys_Printf("gtk_file_selection_set_filename... %p (%s)", file_sel, new_path);
 #endif
       gtk_file_selection_set_filename (GTK_FILE_SELECTION (file_sel), new_path);
       delete[] new_path;
@@ -1390,15 +1459,15 @@ const char* file_dialog (void *parent, gboolean open, const char* title, const c
 
     gtk_grab_add (file_sel);
 #ifdef FILEDLG_DBG
-    Sys_Printf("gtk_widget_show... %p", file_sel); 
+    Sys_Printf("gtk_widget_show... %p", file_sel);
 #endif
     gtk_widget_show (file_sel);
 #ifdef FILEDLG_DBG
-    Sys_Printf("Done.\n"); 
+    Sys_Printf("Done.\n");
 #endif
 
 #ifdef FILEDLG_DBG
-    Sys_Printf("gtk_main_iteration..."); 
+    Sys_Printf("gtk_main_iteration...");
 #endif
     while (loop)
       gtk_main_iteration ();
@@ -1413,7 +1482,7 @@ const char* file_dialog (void *parent, gboolean open, const char* title, const c
 #ifdef FILEDLG_DBG
     Sys_Printf("Done.\n");
 #endif
-    
+
     gtk_grab_remove (file_sel);
     gtk_widget_destroy (file_sel);
 #ifdef _WIN32
@@ -1429,17 +1498,33 @@ const char* file_dialog (void *parent, gboolean open, const char* title, const c
       *w = '/';
 
 #if defined(WIN32)
-  if (g_PrefsDlg.m_bNativeGUI) // filetype mask not supported in gtk dialog yet
+  if (g_PrefsDlg.m_bNativeGUI)
   {
-    // when saving, force an extension depending on filetype
     /* \todo SPoG - file_dialog should return filetype information separately.. not force file extension.. */
     if(!open && pattern != NULL)
     {
       // last ext separator
       w = strrchr(szFile, '.');
-      // no extension
-      w = (w!=NULL) ? w : szFile+strlen(szFile);
-      strcpy(w, type.pattern+1);
+      if (w == NULL) { // No extension.
+        w = szFile + strlen(szFile);
+        strcpy(w, type.pattern + 1); // Add extension of selected filter type.
+      }
+      else { // An extension was explicitly in the filename.
+        int knownExtension = 0;
+        for (int i = typelist.GetNumTypes() - 1; i >= 0; i--) {
+          type = typelist.GetTypeForIndex(i);
+          if (stricmp(w, type.pattern + 1) == 0) {
+            knownExtension = 1;
+            break;
+          }
+        }
+        if (!knownExtension) {
+          if (gtk_MessageBox(parent, "Unknown file extension for this save operation.\nAttempt to save anyways?",
+                             "GtkRadiant", MB_YESNO) == IDNO) {
+            return NULL;
+          }
+        }
+      }
     }
   }
 #endif
@@ -1474,8 +1559,9 @@ char* WINAPI dir_dialog (void *parent, const char* title, const char* path)
                       GTK_SIGNAL_FUNC (dialog_delete_callback), NULL);
   gtk_file_selection_hide_fileop_buttons (GTK_FILE_SELECTION (file_sel));
 
-  if (parent != NULL)
-    gtk_window_set_transient_for (GTK_WINDOW (file_sel), GTK_WINDOW (parent));
+       if ( parent != NULL ) {
+               gtk_window_set_transient_for( GTK_WINDOW( file_sel ), GTK_WINDOW( parent ) );
+       }
 
   gtk_widget_hide (GTK_FILE_SELECTION (file_sel)->file_list->parent);
 
@@ -1491,10 +1577,10 @@ char* WINAPI dir_dialog (void *parent, const char* title, const char* path)
   while (loop)
     gtk_main_iteration ();
 
-  filename = g_strdup(gtk_file_selection_get_filename (GTK_FILE_SELECTION (file_sel)));
+  filename = g_strdup( gtk_file_selection_get_filename( GTK_FILE_SELECTION( file_sel ) ) );
 
-  gtk_grab_remove (file_sel);
-  gtk_widget_destroy (file_sel);
+  gtk_grab_remove( file_sel );
+  gtk_widget_destroy( file_sel );
 
   return filename;
 }