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