]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/gtkmisc.cpp
Another Windows file dialog change in Rambetter-temp-fixes branch.
[xonotic/netradiant.git] / radiant / gtkmisc.cpp
1 /*
2 Copyright (c) 2001, Loki software, inc.
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
7
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
10
11 Redistributions in binary form must reproduce the above copyright notice, this
12 list of conditions and the following disclaimer in the documentation and/or
13 other materials provided with the distribution.
14
15 Neither the name of Loki software nor the names of its contributors may be used
16 to endorse or promote products derived from this software without specific prior
17 written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23 DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 //
32 // Small functions to help with GTK
33 //
34
35 #include <gdk/gdkkeysyms.h>
36 #include <glib/gi18n.h>
37
38 #if defined (__linux__) || defined (__APPLE__)
39 #include <unistd.h>
40 #endif
41
42 #include <gtk/gtk.h>
43
44 #ifdef _WIN32
45 #include <gdk/gdkwin32.h>
46 #define WIN32_LEAN_AND_MEAN
47 #include <windows.h>
48 #endif
49
50
51
52 #ifdef _WIN32
53 #include <io.h>
54 #include <direct.h>
55 #define R_OK 04
56 #endif
57 #include "stdafx.h"
58
59 // =============================================================================
60 // Misc stuff
61
62 // NOTE TTimo window position saving has always been tricky
63 //   it doesn't work the same between win32 and linux .. see below that code is fairly different
64 //   it's also very poorly done, the save calls are a bit randomly disctributed in the OnDestroy
65
66 void save_window_pos (GtkWidget *wnd, window_position_t& pos)
67 {
68   if ((wnd == NULL) || (wnd->window == NULL))
69     return;
70
71   get_window_pos(wnd, &pos.x, &pos.y);
72
73   pos.w = wnd->allocation.width;
74   pos.h = wnd->allocation.height;
75
76 #ifdef DBG_WINDOWPOS
77   //Sys_Printf("save_window_pos 'Window %s'\n",buf);
78 #endif
79 }
80
81 #ifdef _WIN32
82 void win32_get_window_pos(GtkWidget *widget, gint *x, gint *y)
83 {
84   if ( g_PrefsDlg.m_bStartOnPrimMon ) {
85     RECT rc;
86     POINT point;
87     HWND xwnd = (HWND)GDK_WINDOW_HWND (widget->window);
88     const GdkRectangle primaryMonitorRect = g_pParentWnd->GetPrimaryMonitorRect();
89
90     GetClientRect(xwnd,&rc);
91     point.x=rc.left;
92     point.y=rc.top;
93     ClientToScreen(xwnd,&point);
94
95     *x=point.x;
96     *y=point.y;
97
98     *x=max(*x,-widget->allocation.width+10);
99     *x=min(*x,primaryMonitorRect.width-10);
100     *y=max(*y,-widget->allocation.height+10);
101     *y=min(*y,primaryMonitorRect.height-10);
102   } else {
103     // this is the same as the unix version of get_window_pos
104     gdk_window_get_root_origin (widget->window, x, y);
105   }
106 #ifdef DBG_WINDOWPOS
107   Sys_Printf("win32_get_window_pos %p %d,%d\n",widget,*x,*y);
108 #endif
109 }
110 #endif
111
112 void load_window_pos (GtkWidget *wnd, window_position_t& pos)
113 {
114 #ifdef _WIN32
115   const GdkRectangle primaryMonitorRect = g_pParentWnd->GetPrimaryMonitorRect();
116
117   if(pos.x < primaryMonitorRect.x
118     || pos.y < primaryMonitorRect.y
119     || pos.x > primaryMonitorRect.x + primaryMonitorRect.width
120     || pos.y > primaryMonitorRect.y + primaryMonitorRect.height)
121     gtk_window_set_position(GTK_WINDOW(wnd), GTK_WIN_POS_CENTER_ON_PARENT);
122 #else
123   // FIXME: not multihead safe
124   if(pos.x < 0
125     || pos.y < 0
126     || pos.x > gdk_screen_width ()
127     || pos.y > gdk_screen_height ())
128     gtk_window_set_position(GTK_WINDOW(wnd), GTK_WIN_POS_CENTER_ON_PARENT);
129 #endif
130   else
131     gtk_window_move(GTK_WINDOW(wnd), pos.x, pos.y);
132
133   gtk_window_set_default_size (GTK_WINDOW (wnd), pos.w, pos.h);
134 #ifdef DBG_WINDOWPOS
135   Sys_Printf("load_window_pos %p 'Window,%s'\n",wnd,windowData);
136 #endif
137 }
138
139 gint widget_delete_hide (GtkWidget *widget)
140 {
141   gtk_widget_hide (widget);
142
143   return TRUE;
144 }
145
146
147 // Thanks to Mercury, Fingolfin - ETG
148 int readLongLE(FILE *file, unsigned long *m_bytesRead, int *value)
149 {
150   byte buf[4];
151   int len = fread(buf, 4, 1, file);
152   *m_bytesRead += 4;
153   if (len != 1)
154     return -1;
155
156   *value = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
157   return 0;
158 }
159
160 short readShortLE(FILE *file, unsigned long *m_bytesRead, short unsigned *value)
161 {
162   byte buf[2];
163   int len = fread(buf, 2, 1, file);
164   *m_bytesRead += 2;
165   if (len != 1)
166     return -1;
167
168   *value = buf[0] | buf[1] << 8;
169   return 0;
170 }
171
172 unsigned char *load_bitmap_file (const char* filename, guint16 *width, guint16 *height)
173 {
174   int bmWidth, bmHeight;
175   short unsigned bmPlanes, bmBitsPixel;
176   typedef struct {
177     unsigned char rgbBlue;
178     unsigned char rgbGreen;
179     unsigned char rgbRed;
180     unsigned char rgbReserved;
181   } RGBQUAD;
182   unsigned char m1,m2;
183   int sizeimage;
184   short unsigned res1,res2;
185   int filesize, pixoff;
186   int bmisize, compression;
187   int xscale, yscale;
188   int colors, impcol;
189   unsigned long m_bytesRead = 0;
190   unsigned char *imagebits = NULL;
191   FILE *fp;
192
193   *width = *height = 0;
194
195   fp = fopen(filename,"rb");
196   if (fp == NULL)
197   {
198         return NULL;
199   }
200
201   size_t rc;
202   rc = fread(&m1, 1, 1, fp);
203   m_bytesRead++;
204   if (rc == -1)
205   {
206       fclose(fp);
207       return NULL;
208   }
209
210   rc = fread(&m2, 1, 1, fp);
211   m_bytesRead++;
212   if ((m1!='B') || (m2!='M'))
213   {
214       fclose(fp);
215       return NULL;
216   }
217
218   if (readLongLE(fp,&m_bytesRead,&filesize)) {
219         fclose(fp);
220         return NULL;
221   }
222
223   if (readShortLE(fp,&m_bytesRead,&res1)) {
224         fclose(fp);
225         return NULL;
226   }
227
228   if (readShortLE(fp,&m_bytesRead,&res2)) {
229         fclose(fp);
230         return NULL;
231   }
232
233   if (readLongLE(fp,&m_bytesRead,&pixoff)) {
234         fclose(fp);
235         return NULL;
236   }
237
238   if (readLongLE(fp,&m_bytesRead,&bmisize)) {
239         fclose(fp);
240         return NULL;
241   }
242
243   if (readLongLE(fp,&m_bytesRead,&bmWidth)) {
244         fclose(fp);
245         return NULL;
246   }
247
248   if (readLongLE(fp,&m_bytesRead,&bmHeight)) {
249         fclose(fp);
250         return NULL;
251   }
252
253   if (readShortLE(fp,&m_bytesRead,&bmPlanes)) {
254         fclose(fp);
255         return NULL;
256   }
257
258   if (readShortLE(fp,&m_bytesRead,&bmBitsPixel)) {
259         fclose(fp);
260         return NULL;
261   }
262
263   if (readLongLE(fp,&m_bytesRead,&compression)) {
264         fclose(fp);
265         return NULL;
266   }
267
268   if (readLongLE(fp,&m_bytesRead,&sizeimage)) {
269         fclose(fp);
270         return NULL;
271   }
272
273   if (readLongLE(fp,&m_bytesRead,&xscale)) {
274         fclose(fp);
275         return NULL;
276   }
277
278   if (readLongLE(fp,&m_bytesRead,&yscale)) {
279         fclose(fp);
280         return NULL;
281   }
282
283   if (readLongLE(fp,&m_bytesRead,&colors)) {
284         fclose(fp);
285         return NULL;
286   }
287
288   if (readLongLE(fp,&m_bytesRead,&impcol)) {
289         fclose(fp);
290         return NULL;
291   }
292
293   if (colors == 0)
294     colors = 1 << bmBitsPixel;
295
296   RGBQUAD *colormap = NULL;
297   if (bmBitsPixel != 24)
298   {
299         colormap = new RGBQUAD[colors];
300         if (colormap == NULL)
301         {
302           fclose(fp);
303           return NULL;
304         }
305
306         int i;
307         for (i = 0; i < colors; i++)
308         {
309           unsigned char r ,g, b, dummy;
310
311           rc = fread(&b, 1, 1, fp);
312           m_bytesRead++;
313           if (rc!=1)
314           {
315                 delete [] colormap;
316                 fclose(fp);
317                 return NULL;
318           }
319
320           rc = fread(&g, 1, 1, fp);
321           m_bytesRead++;
322           if (rc!=1)
323           {
324                 delete [] colormap;
325                 fclose(fp);
326                 return NULL;
327           }
328
329           rc = fread(&r, 1, 1, fp);
330           m_bytesRead++;
331           if (rc != 1)
332           {
333                 delete [] colormap;
334                 fclose(fp);
335                 return NULL;
336           }
337
338           rc = fread(&dummy, 1, 1, fp);
339           m_bytesRead++;
340           if (rc != 1)
341           {
342                 delete [] colormap;
343                 fclose(fp);
344                 return NULL;
345           }
346
347           colormap[i].rgbRed=r;
348           colormap[i].rgbGreen=g;
349           colormap[i].rgbBlue=b;
350     }
351   }
352
353   if ((long)m_bytesRead > pixoff)
354   {
355         delete [] colormap;
356         fclose(fp);
357         return NULL;
358   }
359
360   while ((long)m_bytesRead < pixoff)
361   {
362         char dummy;
363         fread(&dummy,1,1,fp);
364         m_bytesRead++;
365   }
366
367   int w = bmWidth;
368   int h = bmHeight;
369
370   // set the output params
371   imagebits = (unsigned char *)malloc(w * h * 3);
372   long row_size = w * 3;
373
374   if (imagebits != NULL)
375   {
376           *width = w;
377           *height = h;
378           unsigned char *outbuf = imagebits;
379           long row = 0;
380           long rowOffset = 0;
381
382           if (compression == 0) // BI_RGB
383     {
384             // read rows in reverse order
385             for (row = bmHeight - 1; row >= 0; row--)
386             {
387                     // which row are we working on?
388                     rowOffset = (long unsigned)row * row_size;
389
390                     if (bmBitsPixel == 24)
391                     {
392                       for (int col=0;col<w;col++)
393                       {
394                             long offset = col * 3;
395                             char pixel[3];
396
397                             if (fread((void *)(pixel),1,3,fp) == 3)
398                             {
399                               // we swap red and blue here
400                               *(outbuf + rowOffset + offset + 0) = pixel[2];  // r
401                               *(outbuf + rowOffset + offset + 1) = pixel[1];  // g
402                               *(outbuf + rowOffset + offset + 2) = pixel[0];  // b
403                             }
404               }
405                       m_bytesRead += row_size;
406
407                       // read DWORD padding
408                       while ((m_bytesRead - pixoff) & 3)
409                       {
410                               char dummy;
411                               if (fread(&dummy,1,1,fp) != 1)
412             {
413                           free(imagebits);
414                           fclose(fp);
415                           return NULL;
416             }
417                               m_bytesRead++;
418           }
419         }
420         else
421         {
422                       // pixels are packed as 1 , 4 or 8 bit vals. need to unpack them
423                       int bit_count = 0;
424                       unsigned long mask = (1 << bmBitsPixel) - 1;
425                       unsigned char inbyte = 0;
426
427                       for (int col = 0; col < w; col++)
428           {
429                         int pix = 0;
430
431                         // if we need another byte
432                         if (bit_count <= 0)
433                               {
434                                 bit_count = 8;
435                                 if (fread(&inbyte,1,1,fp) != 1)
436                                 {
437                                         free(imagebits);
438                                         delete [] colormap;
439                                         fclose(fp);
440                                         return NULL;
441                                 }
442                                 m_bytesRead++;
443                               }
444
445                               // keep track of where we are in the bytes
446                               bit_count -= bmBitsPixel;
447                               pix = ( inbyte >> bit_count) & mask;
448
449                               // lookup the color from the colormap - stuff it in our buffer
450                               // swap red and blue
451                               *(outbuf + rowOffset + col * 3 + 2) = colormap[pix].rgbBlue;
452                               *(outbuf + rowOffset + col * 3 + 1) = colormap[pix].rgbGreen;
453                               *(outbuf + rowOffset + col * 3 + 0) = colormap[pix].rgbRed;
454           }
455
456                       // read DWORD padding
457                       while ((m_bytesRead - pixoff) & 3)
458           {
459             char dummy;
460             if (fread(&dummy,1,1,fp)!=1)
461             {
462               free(imagebits);
463               if (colormap)
464                 delete [] colormap;
465               fclose(fp);
466               return NULL;
467             }
468             m_bytesRead++;
469           }
470         }
471       }
472     }
473           else
474           {
475             int i, x = 0;
476             unsigned char c, c1 = 0, *pp;
477             row = 0;
478             pp = outbuf + (bmHeight - 1) * bmWidth * 3;
479
480             if (bmBitsPixel == 8)
481             {
482                     while (row < bmHeight)
483                     {
484                       c = getc(fp);
485
486                       if (c)
487                       {
488                               // encoded mode
489                               c1 = getc(fp);
490                               for (i = 0; i < c; x++, i++)
491                               {
492                                 *pp = colormap[c1].rgbRed; pp++;
493                                 *pp = colormap[c1].rgbGreen; pp++;
494                                 *pp = colormap[c1].rgbBlue; pp++;
495                               }
496                       }
497                       else
498                       {
499                               // c==0x00,  escape codes
500                               c = getc(fp);
501                               if (c == 0x00) // end of line
502                               {
503                                 row++;
504                                 x = 0;
505                                 pp = outbuf + (bmHeight - row - 1) * bmWidth * 3;
506                               }
507                               else if (c == 0x01)
508                                 break; // end of pic
509                               else if (c == 0x02) // delta
510                               {
511                                 c = getc(fp);
512                                 x += c;
513                                 c = getc(fp);
514                                 row += c;
515                                 pp = outbuf + x*3 + (bmHeight - row - 1) * bmWidth * 3;
516                               }
517                               else // absolute mode
518                               {
519                                 for (i = 0; i < c; x++, i++)
520                                 {
521                                         c1 = getc(fp);
522                                         *pp = colormap[c1].rgbRed; pp++;
523                                         *pp = colormap[c1].rgbGreen; pp++;
524                                         *pp = colormap[c1].rgbBlue; pp++;
525                                 }
526
527                                 if (c & 1)
528                                       getc(fp); // odd length run: read an extra pad byte
529                               }
530                       }
531                     }
532             }
533             else if (bmBitsPixel == 4)
534             {
535                     while (row < bmHeight)
536                     {
537                       c = getc(fp);
538
539                       if (c)
540                       {
541                               // encoded mode
542                               c1 = getc(fp);
543                               for (i = 0; i < c; x++, i++)
544                               {
545                                 *pp = colormap[(i&1) ? (c1 & 0x0f) : ((c1>>4)&0x0f)].rgbRed; pp++;
546                                 *pp = colormap[(i&1) ? (c1 & 0x0f) : ((c1>>4)&0x0f)].rgbGreen; pp++;
547                                 *pp = colormap[(i&1) ? (c1 & 0x0f) : ((c1>>4)&0x0f)].rgbBlue; pp++;
548                               }
549                       }
550                       else
551                       {
552                               // c==0x00,  escape codes
553                               c = getc(fp);
554
555                               if (c == 0x00) // end of line
556                               {
557                                 row++;
558                                 x = 0;
559                                 pp = outbuf + (bmHeight - row - 1) * bmWidth * 3;
560                               }
561                               else if (c == 0x01)
562                                 break; // end of pic
563                               else if (c == 0x02) // delta
564                               {
565                                 c = getc(fp);
566                                 x += c;
567                                 c = getc(fp);
568                                 row += c;
569                                 pp = outbuf + x * 3 + (bmHeight - row - 1) * bmWidth * 3;
570                               }
571                               else // absolute mode
572                               {
573                                 for (i = 0; i < c; x++, i++)
574                                 {
575                                         if ((i&1) == 0)
576                                           c1 = getc(fp);
577                                         *pp = colormap[(i&1) ? (c1 & 0x0f) : ((c1>>4)&0x0f)].rgbRed; pp++;
578                                         *pp = colormap[(i&1) ? (c1 & 0x0f) : ((c1>>4)&0x0f)].rgbGreen; pp++;
579                                         *pp = colormap[(i&1) ? (c1 & 0x0f) : ((c1>>4)&0x0f)].rgbBlue; pp++;
580                                 }
581
582                                 if (((c & 3) == 1) || ((c & 3) == 2))
583                                       getc(fp); // odd length run: read an extra pad byte
584             }
585           }
586                     }
587             }
588           }
589           if (colormap)
590             delete [] colormap;
591
592           fclose(fp);
593   }
594   return imagebits;
595 }
596
597 void bmp_to_pixmap (const char* filename, GdkPixmap **pixmap, GdkBitmap **mask)
598 {
599   guint16 width, height;
600   unsigned char *buf;
601   GdkWindow *window = gdk_get_default_root_window();
602   GdkColormap *colormap;
603   GdkGC* gc = gdk_gc_new (window);
604   int i, j;
605   bool hasMask = false;
606
607   *pixmap = *mask = NULL;
608   buf = load_bitmap_file (filename, &width, &height);
609   if (!buf)
610     return;
611
612   colormap = gdk_drawable_get_colormap (window);
613   *pixmap = gdk_pixmap_new (window, width, height, -1);
614
615   typedef struct
616   {
617     GdkColor c;
618     unsigned char *p;
619   } PAL;
620
621   for (i = 0; i < height; i++)
622   {
623     for (j = 0; j < width; j++)
624     {
625       unsigned char *p = &buf[(i * width + j) * 3];
626       PAL pe;
627
628       pe.c.red = (gushort)(p[0] * 0xFF);
629       pe.c.green = (gushort)(p[1] * 0xFF);
630       pe.c.blue = (gushort)(p[2] * 0xFF);
631       gdk_colormap_alloc_color(colormap, &pe.c, FALSE, TRUE);
632       gdk_gc_set_foreground(gc, &pe.c);
633       gdk_draw_point(*pixmap, gc, j, i);
634
635       if (p[0] == 0xFF && p[1] == 0x00 && p[2] == 0xFF)
636         hasMask = true;
637     }
638   }
639
640   gdk_gc_unref (gc);
641   *mask = gdk_pixmap_new (window, width, height, 1);
642   gc = gdk_gc_new (*mask);
643   if (hasMask)
644   {
645     for (i = 0; i < height; i++)
646     {
647       for (j = 0; j < width; j++)
648       {
649               GdkColor mask_pattern;
650
651               // pink is transparent
652               if ((buf[(i*width+j)*3] == 0xff) &&
653                       (buf[(i*width+j)*3+1] == 0x00) &&
654                       (buf[(i*width+j)*3+2] == 0xff))
655                       mask_pattern.pixel = 0;
656         else
657                       mask_pattern.pixel = 1;
658
659         gdk_gc_set_foreground (gc, &mask_pattern);
660         // possible Win32 Gtk bug here
661         //gdk_draw_point (*mask, gc, j, i);
662         gdk_draw_line (*mask, gc, j, i, j + 1, i);
663       }
664     }
665   }
666   else
667   {
668     GdkColor mask_pattern;
669     mask_pattern.pixel = 1;
670     gdk_gc_set_foreground (gc, &mask_pattern);
671     gdk_draw_rectangle (*mask, gc, 1, 0, 0, width, height);
672   }
673   gdk_gc_unref(gc);
674   free (buf);
675 }
676
677 void load_pixmap (const char* filename, GtkWidget* widget, GdkPixmap **gdkpixmap, GdkBitmap **mask)
678 {
679   CString str;
680
681   str = g_strBitmapsPath;
682   str += filename;
683
684   bmp_to_pixmap (str.GetBuffer (), gdkpixmap, mask);
685   if (*gdkpixmap == NULL)
686   {
687     printf("gdkpixmap was null\n");
688     gchar *dummy[] = { "1 1 1 1", "  c None", " " };
689     printf("calling gdk_pixmap_create_from_xpm_d\n");
690     *gdkpixmap = gdk_pixmap_create_from_xpm_d (gdk_get_default_root_window(), mask, NULL, dummy);
691   }
692 }
693
694 // this is the same as above but used by the plugins
695 // GdkPixmap **gdkpixmap, GdkBitmap **mask
696 bool WINAPI load_plugin_bitmap (const char* filename, void **gdkpixmap, void **mask)
697 {
698   CString str;
699
700   str = g_strGameToolsPath;
701   str += g_strPluginsDir;
702   str += "bitmaps/";
703   str += filename;
704   bmp_to_pixmap (str.GetBuffer (), (GdkPixmap **)gdkpixmap, (GdkBitmap **)mask);
705
706   if (*gdkpixmap == NULL)
707   {
708     // look in the core plugins
709     str = g_strAppPath;
710     str += g_strPluginsDir;
711     str += "bitmaps/";
712     str += filename;
713     bmp_to_pixmap (str.GetBuffer (), (GdkPixmap **)gdkpixmap, (GdkBitmap **)mask);
714
715     if (*gdkpixmap == NULL)
716     {
717
718       // look in core modules
719       str = g_strAppPath;
720       str += g_strModulesDir;
721       str += "bitmaps/";
722       str += filename;
723       bmp_to_pixmap (str.GetBuffer (), (GdkPixmap **)gdkpixmap, (GdkBitmap **)mask);
724
725       if (*gdkpixmap == NULL)
726       {
727         gchar *dummy[] = { "1 1 1 1", "  c None", " " };
728         *gdkpixmap = gdk_pixmap_create_from_xpm_d (gdk_get_default_root_window(), (GdkBitmap **)mask, NULL, dummy);
729         return false;
730       }
731     }
732   }
733   return true;
734 }
735
736 // Load a xpm file and return a pixmap widget.
737 GtkWidget* new_pixmap (GtkWidget* widget, const char* filename)
738 {
739   GdkPixmap *gdkpixmap;
740   GdkBitmap *mask;
741   GtkWidget *pixmap;
742
743   load_pixmap (filename, widget, &gdkpixmap, &mask);
744   pixmap = gtk_pixmap_new (gdkpixmap, mask);
745
746   gdk_drawable_unref (gdkpixmap);
747   gdk_drawable_unref (mask);
748
749   return pixmap;
750 }
751
752 // =============================================================================
753 // Menu stuff
754
755 GtkWidget* menu_separator (GtkWidget *menu)
756 {
757   GtkWidget *menu_item = gtk_menu_item_new ();
758   gtk_menu_append (GTK_MENU (menu), menu_item);
759   gtk_widget_set_sensitive (menu_item, FALSE);
760   gtk_widget_show (menu_item);
761   return menu_item;
762 }
763
764 GtkWidget* menu_tearoff (GtkWidget *menu)
765 {
766   GtkWidget *menu_item = gtk_tearoff_menu_item_new ();
767   gtk_menu_append (GTK_MENU (menu), menu_item);
768 // gtk_widget_set_sensitive (menu_item, FALSE); -- controls whether menu is detachable
769   gtk_widget_show (menu_item);
770   return menu_item;
771 }
772
773 GtkWidget* create_sub_menu_with_mnemonic (GtkWidget *bar, const gchar *mnemonic)
774 {
775   GtkWidget *item, *sub_menu;
776
777   item = gtk_menu_item_new_with_mnemonic (mnemonic);
778   gtk_widget_show (item);
779   gtk_container_add (GTK_CONTAINER (bar), item);
780
781   sub_menu = gtk_menu_new ();
782   gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), sub_menu);
783
784   return sub_menu;
785 }
786
787 extern void AddMenuItem (GtkWidget* menu, unsigned int id);
788
789 GtkWidget* create_menu_item_with_mnemonic (GtkWidget *menu, const gchar *mnemonic, GtkSignalFunc func, int id)
790 {
791   GtkWidget *item;
792
793   item = gtk_menu_item_new_with_mnemonic (mnemonic);
794
795   gtk_widget_show (item);
796   gtk_container_add (GTK_CONTAINER (menu), item);
797   gtk_signal_connect (GTK_OBJECT (item), "activate", GTK_SIGNAL_FUNC (func), GINT_TO_POINTER (id));
798
799   AddMenuItem (item, id);
800   return item;
801 }
802
803 GtkWidget* create_check_menu_item_with_mnemonic (GtkWidget *menu, const gchar *mnemonic, GtkSignalFunc func, int id, gboolean active)
804 {
805   GtkWidget *item;
806
807   item = gtk_check_menu_item_new_with_mnemonic(mnemonic);
808
809   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), active);
810   gtk_widget_show (item);
811   gtk_container_add (GTK_CONTAINER (menu), item);
812   gtk_signal_connect (GTK_OBJECT (item), "activate", GTK_SIGNAL_FUNC (func), GINT_TO_POINTER (id));
813
814   AddMenuItem (item, id);
815   return item;
816 }
817
818 GtkWidget* create_radio_menu_item_with_mnemonic (GtkWidget *menu, GtkWidget *last, const gchar *mnemonic, GtkSignalFunc func, int id, gboolean state)
819 {
820   GtkWidget *item;
821   GSList *group = (GSList*)NULL;
822
823   if (last != NULL)
824     group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (last));
825   item = gtk_radio_menu_item_new_with_mnemonic (group, mnemonic);
826   gtk_check_menu_item_set_state (GTK_CHECK_MENU_ITEM (item), state);
827
828   gtk_widget_show (item);
829   gtk_container_add (GTK_CONTAINER (menu), item);
830   gtk_signal_connect (GTK_OBJECT (item), "activate", GTK_SIGNAL_FUNC (func), GINT_TO_POINTER (id));
831
832   AddMenuItem (item, id);
833   return item;
834 }
835
836 GtkWidget* create_menu_in_menu_with_mnemonic (GtkWidget *menu, const gchar *mnemonic)
837 {
838   GtkWidget *item, *submenu;
839
840   item = gtk_menu_item_new_with_mnemonic(mnemonic);
841   gtk_widget_show (item);
842   gtk_container_add (GTK_CONTAINER (menu), item);
843
844   submenu = gtk_menu_new ();
845   gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
846
847   return submenu;
848 }
849
850 // =============================================================================
851 // Message Boxes
852
853 void dialog_button_callback( GtkWidget *widget, gpointer data ) {
854   GtkWidget *parent;
855   int *loop, *ret;
856
857   parent = gtk_widget_get_toplevel( widget );
858   loop = (int*)g_object_get_data( G_OBJECT( parent ), "loop" );
859   ret = (int*)g_object_get_data( G_OBJECT( parent ), "ret" );
860
861   *loop = 0;
862   *ret = GPOINTER_TO_INT (data);
863 }
864
865 gint dialog_delete_callback (GtkWidget *widget, GdkEvent* event, gpointer data)
866 {
867   int *loop;
868
869   gtk_widget_hide (widget);
870   loop = (int*)g_object_get_data (G_OBJECT (widget), "loop");
871   *loop = 0;
872
873   return TRUE;
874 }
875
876 gint dialog_url_callback (GtkWidget *widget, GdkEvent* event, gpointer data)
877 {
878   OpenURL((const char *)g_object_get_data (G_OBJECT (widget), "URL"));
879
880   return TRUE;
881 }
882
883 int WINAPI gtk_MessageBox (void *parent, const char* lpText, const char* lpCaption, guint32 uType, const char* URL)
884 {
885   GtkWidget *window, *w, *vbox, *hbox;
886   GtkAccelGroup *accel;
887   int mode = (uType & MB_TYPEMASK), ret, loop = 1;
888
889   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
890   gtk_signal_connect (GTK_OBJECT (window), "delete_event",
891                       GTK_SIGNAL_FUNC (dialog_delete_callback), NULL);
892   gtk_signal_connect (GTK_OBJECT (window), "destroy",
893                       GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL);
894   gtk_window_set_title (GTK_WINDOW (window), lpCaption);
895   gtk_container_border_width (GTK_CONTAINER (window), 10);
896   g_object_set_data (G_OBJECT (window), "loop", &loop);
897   g_object_set_data (G_OBJECT (window), "ret", &ret);
898   gtk_widget_realize (window);
899
900   gtk_window_set_policy(GTK_WINDOW (window),FALSE,FALSE,TRUE);
901
902   if (parent != NULL)
903     gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (parent));
904
905   accel = gtk_accel_group_new ();
906   gtk_window_add_accel_group (GTK_WINDOW (window), accel);
907
908   vbox = gtk_vbox_new (FALSE, 10);
909   gtk_container_add (GTK_CONTAINER (window), vbox);
910   gtk_widget_show (vbox);
911
912   w = gtk_label_new (lpText);
913   gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2);
914   gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
915   gtk_widget_show (w);
916
917   w = gtk_hseparator_new ();
918   gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2);
919   gtk_widget_show (w);
920
921   hbox = gtk_hbox_new (FALSE, 10);
922   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2);
923   gtk_widget_show (hbox);
924
925   if (mode == MB_OK)
926   {
927     w = gtk_button_new_with_label (_("Ok"));
928     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
929     gtk_signal_connect (GTK_OBJECT (w), "clicked",
930                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK));
931     gtk_widget_add_accelerator (w, "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
932     gtk_widget_add_accelerator (w, "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
933     GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
934     gtk_widget_grab_default (w);
935     gtk_widget_show (w);
936     ret = IDOK;
937   }
938   else if (mode ==  MB_OKCANCEL)
939   {
940     w = gtk_button_new_with_label (_("Ok"));
941     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
942     gtk_signal_connect (GTK_OBJECT (w), "clicked",
943                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK));
944     gtk_widget_add_accelerator (w, "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
945     GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
946     gtk_widget_grab_default (w);
947     gtk_widget_show (w);
948
949     w = gtk_button_new_with_label (_("Cancel"));
950     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
951     gtk_signal_connect (GTK_OBJECT (w), "clicked",
952                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL));
953     gtk_widget_add_accelerator (w, "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
954     gtk_widget_show (w);
955     ret = IDCANCEL;
956   }
957   else if (mode == MB_YESNOCANCEL)
958   {
959     w = gtk_button_new_with_label (_("Yes"));
960     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
961     gtk_signal_connect (GTK_OBJECT (w), "clicked",
962                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDYES));
963     GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
964     gtk_widget_grab_default (w);
965     gtk_widget_show (w);
966
967     w = gtk_button_new_with_label (_("No"));
968     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
969     gtk_signal_connect (GTK_OBJECT (w), "clicked",
970                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDNO));
971     gtk_widget_show (w);
972
973     w = gtk_button_new_with_label (_("Cancel"));
974     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
975     gtk_signal_connect (GTK_OBJECT (w), "clicked",
976                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL));
977     gtk_widget_show (w);
978     ret = IDCANCEL;
979   }
980   else /* if (mode == MB_YESNO) */
981   {
982     w = gtk_button_new_with_label (_("Yes"));
983     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
984     gtk_signal_connect (GTK_OBJECT (w), "clicked",
985                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDYES));
986     GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
987     gtk_widget_grab_default (w);
988     gtk_widget_show (w);
989
990     w = gtk_button_new_with_label (_("No"));
991     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
992     gtk_signal_connect (GTK_OBJECT (w), "clicked",
993                         GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDNO));
994     gtk_widget_show (w);
995     ret = IDNO;
996   }
997
998   if (URL)
999   {
1000     w = gtk_button_new_with_label (_("Go to URL"));
1001     gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
1002     gtk_signal_connect (GTK_OBJECT (w), "clicked",
1003                         GTK_SIGNAL_FUNC (dialog_url_callback), NULL);
1004     g_object_set_data (G_OBJECT (w), "URL", (void *)URL);
1005     GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
1006     gtk_widget_grab_default (w);
1007     gtk_widget_show (w);
1008   }
1009
1010
1011   gtk_widget_show (window);
1012   gtk_grab_add (window);
1013
1014   while (loop)
1015     gtk_main_iteration ();
1016
1017   gtk_grab_remove (window);
1018   gtk_widget_destroy (window);
1019
1020   return ret;
1021 }
1022
1023 // =============================================================================
1024 // File dialog
1025
1026 // fenris #3078 WHENHELLISFROZENOVER
1027
1028 //#define FILEDLG_DBG
1029
1030 static void file_sel_callback (GtkWidget *widget, gpointer data)
1031 {
1032   GtkWidget *parent;
1033   int *loop;
1034   bool *success;
1035
1036   parent = gtk_widget_get_toplevel (widget);
1037   loop = (int*)g_object_get_data (G_OBJECT (parent), "loop");
1038   success = (bool*)g_object_get_data (G_OBJECT (parent), "success");
1039
1040   if (GPOINTER_TO_INT (data) == IDOK)
1041     *success = true;
1042
1043 #ifdef FILEDLG_DBG
1044   else
1045     Sys_Printf("file_sel_callback != IDOK\n");
1046 #endif
1047
1048   *loop = 0;
1049 }
1050
1051 #ifdef _WIN32
1052 #include <commdlg.h>
1053 static OPENFILENAME ofn;       /* common dialog box structure   */
1054 static char szDirName[MAX_PATH];    /* directory string              */
1055 static char szFile[MAX_PATH];       /* filename string               */
1056 static char szFileTitle[MAX_PATH];  /* file title string             */
1057 static int i, cbString;        /* integer count variables       */
1058 static HANDLE hf;              /* file handle                   */
1059 #else
1060 static char szFile[QER_MAX_NAMELEN];
1061 #endif
1062
1063 #define FILEDLG_CUSTOM_FILTER_LENGTH 64
1064 // to be used with the advanced file selector
1065
1066 class CFileType : public IFileTypeList
1067 {
1068   struct filetype_copy_t
1069   {
1070     void operator=(const filetype_t& other)
1071     {
1072       m_name = other.name;
1073       m_pattern = other.pattern;
1074     }
1075     string_t m_name;
1076     string_t m_pattern;
1077   };
1078 public:
1079   CFileType()
1080   {
1081     m_nTypes = 0;
1082     m_pTypes = NULL;
1083     m_strWin32Filters = NULL;
1084     m_pstrGTKMasks = NULL;
1085   }
1086
1087   virtual ~CFileType()
1088   {
1089     delete[] m_pTypes;
1090     DestroyWin32Filters();
1091     DestroyGTKMasks();
1092   }
1093
1094   void addType(filetype_t type)
1095   {
1096     filetype_copy_t* newTypes = new filetype_copy_t [m_nTypes+1];
1097     if(m_nTypes > 0)
1098     {
1099       for(int i=0; i<m_nTypes; i++)
1100         newTypes[i] = m_pTypes[i];
1101       delete[] m_pTypes;
1102     }
1103     m_pTypes = newTypes;
1104     m_pTypes[m_nTypes] = type;
1105     m_nTypes++;
1106     ConstructGTKMasks();
1107     ConstructWin32Filters();
1108   }
1109
1110   filetype_t GetTypeForWin32Filter(const char *filter) const
1111   {
1112     for(int i=0; i<m_nTypes; i++)
1113       if(strcmp(m_pTypes[i].m_pattern.c_str(), filter)==0)
1114         return filetype_t(m_pTypes[i].m_name.c_str(), m_pTypes[i].m_pattern.c_str());
1115     return filetype_t();
1116   }
1117
1118   filetype_t GetTypeForGTKMask(const char *mask) const
1119   {
1120     for(int i=0; i<m_nTypes; i++)
1121       if(strcmp(m_pstrGTKMasks[i],mask)==0)
1122         return filetype_t(m_pTypes[i].m_name.c_str(), m_pTypes[i].m_pattern.c_str());
1123     return filetype_t();
1124   }
1125
1126   int GetNumTypes()
1127   {
1128     return m_nTypes;
1129   }
1130
1131   filetype_t GetTypeForIndex(int index) const // Zero-based index.
1132   {
1133     if (index >= 0 && index < m_nTypes)
1134       return filetype_t(m_pTypes[index].m_name.c_str(), m_pTypes[index].m_pattern.c_str());
1135     return filetype_t();
1136   }
1137
1138   char *m_strWin32Filters;
1139   char **m_pstrGTKMasks;
1140 private:
1141   int m_nTypes;
1142   filetype_copy_t *m_pTypes;
1143
1144   void DestroyWin32Filters()
1145   {
1146     delete[] m_strWin32Filters;
1147   }
1148
1149   void ConstructWin32Filters()
1150   {
1151     const char *r;
1152     char *w;
1153     int i;
1154     int len = 0;
1155     DestroyWin32Filters();
1156     for(i=0; i<m_nTypes; i++)
1157       len = len + strlen(m_pTypes[i].m_name.c_str()) + strlen(m_pTypes[i].m_pattern.c_str())*2 + 5;
1158     m_strWin32Filters = new char[len+1]; // length + null char
1159     for(i=0, w = m_strWin32Filters; i<m_nTypes; i++)
1160     {
1161       for(r = m_pTypes[i].m_name.c_str(); *r!='\0'; r++, w++)
1162         *w = *r;
1163       *w++ = ' ';
1164       *w++ = '(';
1165       for(r = m_pTypes[i].m_pattern.c_str(); *r!='\0'; r++, w++)
1166         *w = *r;
1167       *w++ = ')';
1168       *w++ = '\0';
1169       for(r = m_pTypes[i].m_pattern.c_str(); *r!='\0'; r++, w++)
1170         *w = (*r == ',') ? ';' : *r;
1171       *w++ = '\0';
1172     }
1173     m_strWin32Filters[len] = '\0';
1174   }
1175
1176   void DestroyGTKMasks()
1177   {
1178     if(m_pstrGTKMasks != NULL)
1179       for(char **p = m_pstrGTKMasks; *p != NULL; p++)
1180         delete[] *p;
1181     delete[] m_pstrGTKMasks;
1182   }
1183
1184   void ConstructGTKMasks()
1185   {
1186     const char *r;
1187     char *w;
1188     int i;
1189     int len = 0;
1190     DestroyGTKMasks();
1191     m_pstrGTKMasks = new char*[m_nTypes+1];
1192     for(i=0; i<m_nTypes; i++)
1193     {
1194       len = strlen(m_pTypes[i].m_name.c_str()) + strlen(m_pTypes[i].m_pattern.c_str()) + 3;
1195       m_pstrGTKMasks[i] = new char[len+1]; // length + null char
1196       w = m_pstrGTKMasks[i];
1197       for(r = m_pTypes[i].m_name.c_str(); *r!='\0'; r++, w++)
1198         *w = *r;
1199       *w++ = ' ';
1200       *w++ = '<';
1201       for(r = m_pTypes[i].m_pattern.c_str(); *r!='\0'; r++, w++)
1202         *w = *r;
1203       *w++ = '>';
1204       *w++ = '\0';
1205     }
1206     m_pstrGTKMasks[m_nTypes] = NULL;
1207   }
1208
1209 };
1210
1211 #ifdef _WIN32
1212
1213 static int in_file_dialog = 0;
1214
1215 typedef struct {
1216   gboolean open;
1217   OPENFILENAME *ofn;
1218   BOOL dlgRtnVal;
1219   int done;
1220 } win32_native_file_dialog_comms_t;
1221
1222 DWORD WINAPI win32_native_file_dialog_thread_func(LPVOID lpParam)
1223 {
1224   win32_native_file_dialog_comms_t *fileDialogComms;
1225   fileDialogComms = (win32_native_file_dialog_comms_t *) lpParam;
1226   if (fileDialogComms->open) {
1227     fileDialogComms->dlgRtnVal = GetOpenFileName(fileDialogComms->ofn);
1228   }
1229   else {
1230     fileDialogComms->dlgRtnVal = GetSaveFileName(fileDialogComms->ofn);
1231   }
1232   fileDialogComms->done = -1; // No need to synchronize around lock.
1233   return 0;
1234 }
1235
1236 #endif
1237
1238 /**
1239  * @param[in] baseSubDir should have a trailing slash if not @c NULL
1240  */
1241 const char* file_dialog (void *parent, gboolean open, const char* title, const char* path, const char* pattern, const char *baseSubDir)
1242 {
1243
1244 #ifdef _WIN32
1245   HANDLE fileDialogThreadHandle;
1246   win32_native_file_dialog_comms_t fileDialogComms;
1247   int dialogDone;
1248 #endif
1249
1250   // Gtk dialog
1251   GtkWidget* file_sel;
1252   int loop = 1;
1253   char *new_path = NULL;
1254
1255   const char* r;
1256   char* w;
1257   filetype_t type;
1258   CFileType typelist;
1259   if(pattern != NULL)
1260     GetFileTypeRegistry()->getTypeList(pattern, &typelist);
1261
1262 #ifdef FILEDLG_DBG
1263   Sys_Printf("file_dialog: open = %d title = %s path = %s\n", open, title, path);
1264   if (pattern)
1265   {
1266     Sys_Printf("Patterns:\n");
1267     char** p = typelist.m_pstrGTKMasks;
1268     while(*p!=NULL)
1269       Sys_Printf("%s\n", *p++);
1270   }
1271   else
1272     Sys_Printf("no patterns\n");
1273 #endif
1274
1275 #ifdef _WIN32
1276   if (g_PrefsDlg.m_bNativeGUI)
1277   {
1278 #ifdef FILEDLG_DBG
1279     Sys_Printf("Doing win32 file dialog...");
1280 #endif
1281     // do that the native way
1282
1283     if (in_file_dialog) return NULL; // Avoid recursive entry.
1284     in_file_dialog = 1;
1285     /* Set the members of the OPENFILENAME structure. */
1286     // See http://msdn.microsoft.com/en-us/library/ms646839%28v=vs.85%29.aspx .
1287     memset(&ofn, 0, sizeof(ofn));
1288     ofn.lStructSize = sizeof(ofn);
1289     ofn.hwndOwner = (HWND)GDK_WINDOW_HWND (g_pParentWnd->m_pWidget->window);
1290     ofn.nFilterIndex = 1; // The index is 1-based, not 0-based.  This basically says,
1291                           // "select the first filter as default".
1292     if (pattern)
1293     {
1294       ofn.lpstrFilter = typelist.m_strWin32Filters;
1295     }
1296     else
1297     {
1298       // TODO: Would be a bit cleaner if we could extract this string from
1299       // GetFileTypeRegistry() instead of hardcoding it here.
1300       ofn.lpstrFilter = "all files\0*.*\0"; // Second '\0' will be added to end of string.
1301     }
1302     szFile[0] = '\0';
1303     ofn.lpstrFile = szFile;
1304     ofn.nMaxFile = sizeof(szFile);
1305     if(path)
1306     {
1307       // szDirName: Radiant uses unix convention for paths internally
1308       //   Win32 (of course) and Gtk (who would have thought) expect the '\\' convention
1309       // copy path, replacing dir separators as appropriate
1310       for(r=path, w=szDirName; *r!='\0'; r++)
1311         *w++ = (*r=='/') ? '\\' : *r;
1312       // terminate string
1313       *w = '\0';
1314       ofn.lpstrInitialDir = szDirName;
1315     }
1316     ofn.lpstrTitle = title;
1317     ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
1318
1319     memset(&fileDialogComms, 0, sizeof(fileDialogComms));
1320     fileDialogComms.open = open;
1321     fileDialogComms.ofn = &ofn;
1322
1323     fileDialogThreadHandle =
1324       CreateThread(NULL, // lpThreadAttributes
1325                    0, // dwStackSize, default stack size
1326                    win32_native_file_dialog_thread_func, // lpStartAddress, funcion to call
1327                    &fileDialogComms, // lpParameter, argument to pass to function
1328                    0, // dwCreationFlags
1329                    NULL); // lpThreadId
1330
1331     dialogDone = 0;
1332     while (1) {
1333       // Avoid blocking indefinitely.  Another thread will set fileDialogComms->done to nonzero;
1334       // we don't want to be in an indefinite blocked state when this happens.  We want to break
1335       // out of here eventually.
1336       while (gtk_events_pending()) {
1337         gtk_main_iteration();
1338       }
1339       if (dialogDone) break;
1340       if (fileDialogComms.done) dialogDone = 1; // One more loop of gtk_main_iteration() to get things in sync.
1341       // Avoid tight infinte loop, add a small amount of sleep.
1342       Sleep(10);
1343     }
1344     // Make absolutely sure that the thread is finished before we call CloseHandle().
1345     WaitForSingleObject(fileDialogThreadHandle, INFINITE);
1346     CloseHandle(fileDialogThreadHandle);
1347
1348     in_file_dialog = 0;
1349     
1350     if (!fileDialogComms.dlgRtnVal) {
1351       return NULL; // Cancelled.
1352     }
1353
1354     if(pattern != NULL)
1355       type = typelist.GetTypeForIndex(ofn.nFilterIndex - 1);
1356
1357 #ifdef FILEDLG_DBG
1358     Sys_Printf("Done.\n");
1359 #endif
1360   }
1361   else
1362   {
1363 #endif
1364         char buf[PATH_MAX];
1365     // do that the Gtk way
1366     if (title == NULL)
1367       title = open ? _("Open File") : _("Save File");
1368
1369 #ifdef FILEDLG_DBG
1370     Sys_Printf("Doing Gtk file dialog:\nBuilding new_path..");
1371 #endif
1372     // we expect an actual path below, if the path is NULL we might crash
1373     if (!path || path[0] == '\0')
1374     {
1375                 strcpy(buf, g_pGameDescription->mEnginePath.GetBuffer());
1376                 strcat(buf, g_pGameDescription->mBaseGame.GetBuffer());
1377                 strcat(buf, "/");
1378                 if (baseSubDir)
1379                         strcat(buf, baseSubDir);
1380                 path = buf;
1381         }
1382
1383     // alloc new path with extra char for dir separator
1384     new_path = new char[strlen(path)+1+1];
1385     // copy path, replacing dir separators as appropriate
1386     for(r=path, w=new_path; *r!='\0'; r++)
1387       *w++ = (*r=='/') ? G_DIR_SEPARATOR : *r;
1388     // add dir separator to end of path if required
1389     if(*(w-1) != G_DIR_SEPARATOR) *w++ = G_DIR_SEPARATOR;
1390     // terminate string
1391     *w = '\0';
1392
1393 #ifdef FILEDLG_DBG
1394         Sys_Printf("Done.\n");
1395         Sys_Printf("Calling gtk_file_selection_new with title: %s...", title);
1396 #endif
1397     file_sel = gtk_file_selection_new (title);
1398 #ifdef FILEDLG_DBG
1399         Sys_Printf("Done.\n");
1400         Sys_Printf("Set the masks...");
1401 #endif
1402
1403 #if 0 //!\todo Add masks to GtkFileSelection in gtk-2.0
1404     // set the masks
1405     if (pattern)
1406     {
1407       gtk_file_selection_clear_masks (GTK_FILE_SELECTION (file_sel));
1408       gtk_file_selection_set_masks (GTK_FILE_SELECTION (file_sel), const_cast<const char**>(typelist.m_pstrGTKMasks));
1409     }
1410 #endif
1411
1412 #ifdef FILEDLG_DBG
1413     Sys_Printf("Done.\n");
1414 #endif
1415
1416     gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_sel)->ok_button), "clicked",
1417       GTK_SIGNAL_FUNC (file_sel_callback), GINT_TO_POINTER (IDOK));
1418     gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_sel)->cancel_button), "clicked",
1419       GTK_SIGNAL_FUNC (file_sel_callback), GINT_TO_POINTER (IDCANCEL));
1420     gtk_signal_connect (GTK_OBJECT (file_sel), "delete_event",
1421       GTK_SIGNAL_FUNC (dialog_delete_callback), NULL);
1422     gtk_file_selection_hide_fileop_buttons (GTK_FILE_SELECTION (file_sel));
1423
1424     if (parent != NULL)
1425       gtk_window_set_transient_for (GTK_WINDOW (file_sel), GTK_WINDOW (parent));
1426
1427 #ifdef FILEDLG_DBG
1428     Sys_Printf("set_data...");
1429 #endif
1430     bool success = false;
1431     g_object_set_data (G_OBJECT (file_sel), "loop", &loop);
1432     g_object_set_data (G_OBJECT (file_sel), "success", &success);
1433 #ifdef FILEDLG_DBG
1434     Sys_Printf("Done.\n");
1435 #endif
1436
1437     if (!open)
1438     {
1439 #ifdef FILEDLG_DBG
1440       Sys_Printf("set_data \"overwrite\" ...");
1441 #endif
1442       g_object_set_data (G_OBJECT (file_sel), "overwrite", GINT_TO_POINTER (1));
1443 #ifdef FILEDLG_DBG
1444       Sys_Printf("Done.\n");
1445 #endif
1446     }
1447
1448     if (new_path != NULL)
1449     {
1450 #ifdef FILEDLG_DBG
1451       Sys_Printf("gtk_file_selection_set_filename... %p (%s)", file_sel, new_path);
1452 #endif
1453       gtk_file_selection_set_filename (GTK_FILE_SELECTION (file_sel), new_path);
1454       delete[] new_path;
1455 #ifdef FILEDLG_DBG
1456       Sys_Printf("Done.\n");
1457 #endif
1458     }
1459
1460     gtk_grab_add (file_sel);
1461 #ifdef FILEDLG_DBG
1462     Sys_Printf("gtk_widget_show... %p", file_sel);
1463 #endif
1464     gtk_widget_show (file_sel);
1465 #ifdef FILEDLG_DBG
1466     Sys_Printf("Done.\n");
1467 #endif
1468
1469 #ifdef FILEDLG_DBG
1470     Sys_Printf("gtk_main_iteration...");
1471 #endif
1472     while (loop)
1473       gtk_main_iteration ();
1474     if(success)
1475     {
1476 #if 0 //!\todo Add masks to GtkFileSelection in gtk2
1477       if(pattern!=NULL)
1478         type = typelist.GetTypeForGTKMask(GTK_FILE_SELECTION (file_sel)->mask);
1479 #endif
1480       strcpy(szFile, gtk_file_selection_get_filename (GTK_FILE_SELECTION (file_sel)));
1481     }
1482 #ifdef FILEDLG_DBG
1483     Sys_Printf("Done.\n");
1484 #endif
1485
1486     gtk_grab_remove (file_sel);
1487     gtk_widget_destroy (file_sel);
1488 #ifdef _WIN32
1489   }
1490 #endif
1491
1492   // don't return an empty filename
1493   if(szFile[0] == '\0') return NULL;
1494
1495   // convert back to unix format
1496   for(w=szFile; *w!='\0'; w++)
1497     if(*w=='\\')
1498       *w = '/';
1499
1500 #if defined(WIN32)
1501   if (g_PrefsDlg.m_bNativeGUI)
1502   {
1503     /* \todo SPoG - file_dialog should return filetype information separately.. not force file extension.. */
1504     if(!open && pattern != NULL)
1505     {
1506       // last ext separator
1507       w = strrchr(szFile, '.');
1508       if (w == NULL) { // No extension.
1509         w = szFile + strlen(szFile);
1510         strcpy(w, type.pattern + 1); // Add extension of selected filter type.
1511       }
1512       else { // An extension was explicitly in the filename.
1513         int knownExtension = 0;
1514         for (int i = typelist.GetNumTypes() - 1; i >= 0; i--) {
1515           type = typelist.GetTypeForIndex(i);
1516           if (stricmp(w, type.pattern + 1) == 0) {
1517             knownExtension = 1;
1518             break;
1519           }
1520         }
1521         if (!knownExtension) {
1522           if (gtk_MessageBox(parent, "Unknown file extension for this save operation.\nAttempt to save anyways?",
1523                              "GtkRadiant", MB_YESNO) == IDNO) {
1524             return NULL;
1525           }
1526         }
1527       }
1528     }
1529   }
1530 #endif
1531
1532   // prompt to overwrite existing files
1533   if (!open)
1534     if (access (szFile, R_OK) == 0)
1535       if (gtk_MessageBox (parent, "File already exists.\nOverwrite?", "GtkRadiant", MB_YESNO) == IDNO)
1536         return NULL;
1537
1538 #ifdef FILEDLG_DBG
1539   // ... let's use a static filename
1540   Sys_Printf("filename: %p\n", szFile);
1541 #endif
1542
1543   return szFile;
1544 }
1545
1546 char* WINAPI dir_dialog (void *parent, const char* title, const char* path)
1547 {
1548   GtkWidget* file_sel;
1549   char* filename = (char*)NULL;
1550   int loop = 1;
1551   bool success = false;
1552
1553   file_sel = gtk_file_selection_new (title);
1554   gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_sel)->ok_button), "clicked",
1555                       GTK_SIGNAL_FUNC (file_sel_callback), GINT_TO_POINTER (IDOK));
1556   gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_sel)->cancel_button), "clicked",
1557                       GTK_SIGNAL_FUNC (file_sel_callback), GINT_TO_POINTER (IDCANCEL));
1558   gtk_signal_connect (GTK_OBJECT (file_sel), "delete_event",
1559                       GTK_SIGNAL_FUNC (dialog_delete_callback), NULL);
1560   gtk_file_selection_hide_fileop_buttons (GTK_FILE_SELECTION (file_sel));
1561
1562         if ( parent != NULL ) {
1563                 gtk_window_set_transient_for( GTK_WINDOW( file_sel ), GTK_WINDOW( parent ) );
1564         }
1565
1566   gtk_widget_hide (GTK_FILE_SELECTION (file_sel)->file_list->parent);
1567
1568   g_object_set_data (G_OBJECT (file_sel), "loop", &loop);
1569   g_object_set_data (G_OBJECT (file_sel), "success", &success);
1570
1571   if (path != NULL)
1572     gtk_file_selection_set_filename (GTK_FILE_SELECTION (file_sel), path);
1573
1574   gtk_grab_add (file_sel);
1575   gtk_widget_show (file_sel);
1576
1577   while (loop)
1578     gtk_main_iteration ();
1579
1580   filename = g_strdup( gtk_file_selection_get_filename( GTK_FILE_SELECTION( file_sel ) ) );
1581
1582   gtk_grab_remove( file_sel );
1583   gtk_widget_destroy( file_sel );
1584
1585   return filename;
1586 }
1587
1588 bool WINAPI color_dialog (void *parent, float *color, const char* title)
1589 {
1590   GtkWidget* dlg;
1591   double clr[3];
1592   int loop = 1, ret = IDCANCEL;
1593
1594   clr[0] = color[0];
1595   clr[1] = color[1];
1596   clr[2] = color[2];
1597
1598   dlg = gtk_color_selection_dialog_new (title);
1599   gtk_color_selection_set_color (GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (dlg)->colorsel), clr);
1600   gtk_signal_connect (GTK_OBJECT (dlg), "delete_event",
1601                       GTK_SIGNAL_FUNC (dialog_delete_callback), NULL);
1602   gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
1603                       GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL);
1604   gtk_signal_connect (GTK_OBJECT (GTK_COLOR_SELECTION_DIALOG (dlg)->ok_button), "clicked",
1605                       GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK));
1606   gtk_signal_connect (GTK_OBJECT (GTK_COLOR_SELECTION_DIALOG (dlg)->cancel_button), "clicked",
1607                       GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL));
1608   g_object_set_data (G_OBJECT (dlg), "loop", &loop);
1609   g_object_set_data (G_OBJECT (dlg), "ret", &ret);
1610
1611   if (parent != NULL)
1612     gtk_window_set_transient_for (GTK_WINDOW (dlg), GTK_WINDOW (parent));
1613
1614   gtk_widget_show (dlg);
1615   gtk_grab_add (dlg);
1616
1617   while (loop)
1618     gtk_main_iteration ();
1619
1620   GdkColor gdkcolor;
1621   gtk_color_selection_get_current_color (GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (dlg)->colorsel), &gdkcolor);
1622   clr[0] = gdkcolor.red / 65535.0;
1623   clr[1] = gdkcolor.green / 65535.0;
1624   clr[2] = gdkcolor.blue / 65535.0;
1625
1626   gtk_grab_remove (dlg);
1627   gtk_widget_destroy (dlg);
1628
1629   if (ret == IDOK)
1630   {
1631     color[0] = (float)clr[0];
1632     color[1] = (float)clr[1];
1633     color[2] = (float)clr[2];
1634
1635     return true;
1636   }
1637
1638   return false;
1639 }
1640
1641 void OpenURL(const char *url)
1642 {
1643   // let's put a little comment
1644   Sys_Printf("OpenURL: %s\n", url);
1645 #ifdef __linux__
1646   // \todo FIXME: the way we open URLs on *nix should be improved. A script is good (see how I do on RTCW)
1647   char command[2*PATH_MAX];
1648   snprintf( command, sizeof(command), "%s/openurl.sh \"%s\" &", g_strAppPath.GetBuffer(), url );
1649   if (system (command) != 0)
1650     gtk_MessageBox (g_pParentWnd->m_pWidget, "Failed to launch Netscape!");
1651 #endif
1652 #ifdef __APPLE__
1653   char command[2*PATH_MAX];
1654   snprintf (command, sizeof(command),
1655             "open \"%s\" &", url, url);
1656   if (system (command) != 0)
1657     gtk_MessageBox (g_pParentWnd->m_pWidget, "Unable to launch browser!");
1658 #endif
1659 #ifdef _WIN32
1660   ShellExecute( (HWND)GDK_WINDOW_HWND (g_pParentWnd->m_pWidget->window), "open", url, NULL, NULL, SW_SHOW );
1661 #endif
1662 }
1663
1664 void CheckMenuSplitting (GtkWidget *&menu)
1665 {
1666   GtkWidget *item,*menu2;
1667
1668   GtkRequisition requisition;
1669   gint screen_height;
1670
1671   gtk_widget_size_request (GTK_WIDGET (menu), &requisition);
1672   screen_height = gdk_screen_height ();
1673
1674   if ((screen_height - requisition.height) < 20)
1675   {
1676     menu2 = gtk_menu_new ();
1677
1678     // move the last 2 items to a submenu (3 because of win32)
1679     for (int i = 0; i < 3; i++)
1680     {
1681       item = GTK_WIDGET (g_list_last (gtk_container_children (GTK_CONTAINER (menu)))->data);
1682       gtk_widget_ref (item);
1683       gtk_container_remove (GTK_CONTAINER (menu), item);
1684       gtk_menu_append (GTK_MENU (menu2), item);
1685       gtk_widget_unref (item);
1686     }
1687
1688     item = gtk_menu_item_new_with_label ("--------");
1689     gtk_widget_show (item);
1690     gtk_container_add (GTK_CONTAINER (menu), item);
1691     gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu2);
1692     menu = menu2;
1693   }
1694 }