2 Copyright (c) 2001, Loki software, inc.
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
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.
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
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.
32 // Small functions to help with GTK
35 #include <gdk/gdkkeysyms.h>
36 #include <glib/gi18n.h>
38 #if defined ( __linux__ ) || defined ( __APPLE__ )
45 #include <gdk/gdkwin32.h>
46 #define WIN32_LEAN_AND_MEAN
59 // =============================================================================
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
66 void save_window_pos( GtkWidget *wnd, window_position_t& pos ){
67 if ( ( wnd == NULL ) || ( wnd->window == NULL ) ) {
71 get_window_pos( wnd, &pos.x, &pos.y );
73 pos.w = wnd->allocation.width;
74 pos.h = wnd->allocation.height;
77 //Sys_Printf("save_window_pos 'Window %s'\n",buf);
82 void win32_get_window_pos( GtkWidget *widget, gint *x, gint *y ){
83 if ( g_PrefsDlg.m_bStartOnPrimMon ) {
86 HWND xwnd = (HWND)GDK_WINDOW_HWND( widget->window );
87 const GdkRectangle primaryMonitorRect = g_pParentWnd->GetPrimaryMonitorRect();
89 GetClientRect( xwnd,&rc );
92 ClientToScreen( xwnd,&point );
97 *x = max( *x,-widget->allocation.width + 10 );
98 *x = min( *x,primaryMonitorRect.width - 10 );
99 *y = max( *y,-widget->allocation.height + 10 );
100 *y = min( *y,primaryMonitorRect.height - 10 );
103 // this is the same as the unix version of get_window_pos
104 gdk_window_get_root_origin( widget->window, x, y );
107 Sys_Printf( "win32_get_window_pos %p %d,%d\n",widget,*x,*y );
112 void load_window_pos( GtkWidget *wnd, window_position_t& pos ){
114 const GdkRectangle primaryMonitorRect = g_pParentWnd->GetPrimaryMonitorRect();
116 if ( pos.x < primaryMonitorRect.x
117 || pos.y < primaryMonitorRect.y
118 || pos.x > primaryMonitorRect.x + primaryMonitorRect.width
119 || pos.y > primaryMonitorRect.y + primaryMonitorRect.height ) {
120 gtk_window_set_position( GTK_WINDOW( wnd ), GTK_WIN_POS_CENTER_ON_PARENT );
123 // FIXME: not multihead safe
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 );
132 gtk_window_move( GTK_WINDOW( wnd ), pos.x, pos.y );
135 gtk_window_set_default_size( GTK_WINDOW( wnd ), pos.w, pos.h );
137 Sys_Printf( "load_window_pos %p 'Window,%s'\n",wnd,windowData );
141 gint widget_delete_hide( GtkWidget *widget ){
142 gtk_widget_hide( widget );
148 // Thanks to Mercury, Fingolfin - ETG
149 int readLongLE( FILE *file, unsigned long *m_bytesRead, int *value ){
151 int len = fread( buf, 4, 1, file );
157 *value = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
161 short readShortLE( FILE *file, unsigned long *m_bytesRead, short unsigned *value ){
163 int len = fread( buf, 2, 1, file );
169 *value = buf[0] | buf[1] << 8;
173 unsigned char *load_bitmap_file( const char* filename, guint16 *width, guint16 *height ){
174 int bmWidth, bmHeight;
175 short unsigned bmPlanes, bmBitsPixel;
177 unsigned char rgbBlue;
178 unsigned char rgbGreen;
179 unsigned char rgbRed;
180 unsigned char rgbReserved;
184 short unsigned res1,res2;
185 int filesize, pixoff;
186 int bmisize, compression;
189 unsigned long m_bytesRead = 0;
190 unsigned char *imagebits = NULL;
193 *width = *height = 0;
195 fp = fopen( filename,"rb" );
201 rc = fread( &m1, 1, 1, fp );
208 rc = fread( &m2, 1, 1, fp );
210 if ( ( m1 != 'B' ) || ( m2 != 'M' ) ) {
215 if ( readLongLE( fp,&m_bytesRead,&filesize ) ) {
220 if ( readShortLE( fp,&m_bytesRead,&res1 ) ) {
225 if ( readShortLE( fp,&m_bytesRead,&res2 ) ) {
230 if ( readLongLE( fp,&m_bytesRead,&pixoff ) ) {
235 if ( readLongLE( fp,&m_bytesRead,&bmisize ) ) {
240 if ( readLongLE( fp,&m_bytesRead,&bmWidth ) ) {
245 if ( readLongLE( fp,&m_bytesRead,&bmHeight ) ) {
250 if ( readShortLE( fp,&m_bytesRead,&bmPlanes ) ) {
255 if ( readShortLE( fp,&m_bytesRead,&bmBitsPixel ) ) {
260 if ( readLongLE( fp,&m_bytesRead,&compression ) ) {
265 if ( readLongLE( fp,&m_bytesRead,&sizeimage ) ) {
270 if ( readLongLE( fp,&m_bytesRead,&xscale ) ) {
275 if ( readLongLE( fp,&m_bytesRead,&yscale ) ) {
280 if ( readLongLE( fp,&m_bytesRead,&colors ) ) {
285 if ( readLongLE( fp,&m_bytesRead,&impcol ) ) {
291 colors = 1 << bmBitsPixel;
294 RGBQUAD *colormap = NULL;
295 if ( bmBitsPixel != 24 ) {
296 colormap = new RGBQUAD[colors];
297 if ( colormap == NULL ) {
303 for ( i = 0; i < colors; i++ )
305 unsigned char r,g, b, dummy;
307 rc = fread( &b, 1, 1, fp );
315 rc = fread( &g, 1, 1, fp );
323 rc = fread( &r, 1, 1, fp );
331 rc = fread( &dummy, 1, 1, fp );
339 colormap[i].rgbRed = r;
340 colormap[i].rgbGreen = g;
341 colormap[i].rgbBlue = b;
345 if ( (long)m_bytesRead > pixoff ) {
351 while ( (long)m_bytesRead < pixoff )
354 fread( &dummy,1,1,fp );
361 // set the output params
362 imagebits = (unsigned char *)malloc( w * h * 3 );
363 long row_size = w * 3;
365 if ( imagebits != NULL ) {
368 unsigned char *outbuf = imagebits;
372 if ( compression == 0 ) { // BI_RGB
373 // read rows in reverse order
374 for ( row = bmHeight - 1; row >= 0; row-- )
376 // which row are we working on?
377 rowOffset = (long unsigned)row * row_size;
379 if ( bmBitsPixel == 24 ) {
380 for ( int col = 0; col < w; col++ )
382 long offset = col * 3;
385 if ( fread( (void *)( pixel ),1,3,fp ) == 3 ) {
386 // we swap red and blue here
387 *( outbuf + rowOffset + offset + 0 ) = pixel[2]; // r
388 *( outbuf + rowOffset + offset + 1 ) = pixel[1]; // g
389 *( outbuf + rowOffset + offset + 2 ) = pixel[0]; // b
392 m_bytesRead += row_size;
394 // read DWORD padding
395 while ( ( m_bytesRead - pixoff ) & 3 )
398 if ( fread( &dummy,1,1,fp ) != 1 ) {
408 // pixels are packed as 1 , 4 or 8 bit vals. need to unpack them
410 unsigned long mask = ( 1 << bmBitsPixel ) - 1;
411 unsigned char inbyte = 0;
413 for ( int col = 0; col < w; col++ )
417 // if we need another byte
418 if ( bit_count <= 0 ) {
420 if ( fread( &inbyte,1,1,fp ) != 1 ) {
429 // keep track of where we are in the bytes
430 bit_count -= bmBitsPixel;
431 pix = ( inbyte >> bit_count ) & mask;
433 // lookup the color from the colormap - stuff it in our buffer
435 *( outbuf + rowOffset + col * 3 + 2 ) = colormap[pix].rgbBlue;
436 *( outbuf + rowOffset + col * 3 + 1 ) = colormap[pix].rgbGreen;
437 *( outbuf + rowOffset + col * 3 + 0 ) = colormap[pix].rgbRed;
440 // read DWORD padding
441 while ( ( m_bytesRead - pixoff ) & 3 )
444 if ( fread( &dummy,1,1,fp ) != 1 ) {
460 unsigned char c, c1 = 0, *pp;
462 pp = outbuf + ( bmHeight - 1 ) * bmWidth * 3;
464 if ( bmBitsPixel == 8 ) {
465 while ( row < bmHeight )
472 for ( i = 0; i < c; x++, i++ )
474 *pp = colormap[c1].rgbRed; pp++;
475 *pp = colormap[c1].rgbGreen; pp++;
476 *pp = colormap[c1].rgbBlue; pp++;
481 // c==0x00, escape codes
483 if ( c == 0x00 ) { // end of line
486 pp = outbuf + ( bmHeight - row - 1 ) * bmWidth * 3;
488 else if ( c == 0x01 ) {
491 else if ( c == 0x02 ) { // delta
496 pp = outbuf + x * 3 + ( bmHeight - row - 1 ) * bmWidth * 3;
498 else // absolute mode
500 for ( i = 0; i < c; x++, i++ )
503 *pp = colormap[c1].rgbRed; pp++;
504 *pp = colormap[c1].rgbGreen; pp++;
505 *pp = colormap[c1].rgbBlue; pp++;
509 getc( fp ); // odd length run: read an extra pad byte
515 else if ( bmBitsPixel == 4 ) {
516 while ( row < bmHeight )
523 for ( i = 0; i < c; x++, i++ )
525 *pp = colormap[( i & 1 ) ? ( c1 & 0x0f ) : ( ( c1 >> 4 ) & 0x0f )].rgbRed; pp++;
526 *pp = colormap[( i & 1 ) ? ( c1 & 0x0f ) : ( ( c1 >> 4 ) & 0x0f )].rgbGreen; pp++;
527 *pp = colormap[( i & 1 ) ? ( c1 & 0x0f ) : ( ( c1 >> 4 ) & 0x0f )].rgbBlue; pp++;
532 // c==0x00, escape codes
535 if ( c == 0x00 ) { // end of line
538 pp = outbuf + ( bmHeight - row - 1 ) * bmWidth * 3;
540 else if ( c == 0x01 ) {
543 else if ( c == 0x02 ) { // delta
548 pp = outbuf + x * 3 + ( bmHeight - row - 1 ) * bmWidth * 3;
550 else // absolute mode
552 for ( i = 0; i < c; x++, i++ )
554 if ( ( i & 1 ) == 0 ) {
557 *pp = colormap[( i & 1 ) ? ( c1 & 0x0f ) : ( ( c1 >> 4 ) & 0x0f )].rgbRed; pp++;
558 *pp = colormap[( i & 1 ) ? ( c1 & 0x0f ) : ( ( c1 >> 4 ) & 0x0f )].rgbGreen; pp++;
559 *pp = colormap[( i & 1 ) ? ( c1 & 0x0f ) : ( ( c1 >> 4 ) & 0x0f )].rgbBlue; pp++;
562 if ( ( ( c & 3 ) == 1 ) || ( ( c & 3 ) == 2 ) ) {
563 getc( fp ); // odd length run: read an extra pad byte
579 void bmp_to_pixmap( const char* filename, GdkPixmap **pixmap, GdkBitmap **mask ){
580 guint16 width, height;
582 GdkWindow *window = gdk_get_default_root_window();
583 GdkColormap *colormap;
584 GdkGC* gc = gdk_gc_new( window );
586 bool hasMask = false;
588 *pixmap = *mask = NULL;
589 buf = load_bitmap_file( filename, &width, &height );
594 colormap = gdk_drawable_get_colormap( window );
595 *pixmap = gdk_pixmap_new( window, width, height, -1 );
603 for ( i = 0; i < height; i++ )
605 for ( j = 0; j < width; j++ )
607 unsigned char *p = &buf[( i * width + j ) * 3];
610 pe.c.red = (gushort)( p[0] * 0xFF );
611 pe.c.green = (gushort)( p[1] * 0xFF );
612 pe.c.blue = (gushort)( p[2] * 0xFF );
613 gdk_colormap_alloc_color( colormap, &pe.c, FALSE, TRUE );
614 gdk_gc_set_foreground( gc, &pe.c );
615 gdk_draw_point( *pixmap, gc, j, i );
617 if ( p[0] == 0xFF && p[1] == 0x00 && p[2] == 0xFF ) {
624 *mask = gdk_pixmap_new( window, width, height, 1 );
625 gc = gdk_gc_new( *mask );
627 for ( i = 0; i < height; i++ )
629 for ( j = 0; j < width; j++ )
631 GdkColor mask_pattern;
633 // pink is transparent
634 if ( ( buf[( i * width + j ) * 3] == 0xff ) &&
635 ( buf[( i * width + j ) * 3 + 1] == 0x00 ) &&
636 ( buf[( i * width + j ) * 3 + 2] == 0xff ) ) {
637 mask_pattern.pixel = 0;
640 mask_pattern.pixel = 1;
643 gdk_gc_set_foreground( gc, &mask_pattern );
644 // possible Win32 Gtk bug here
645 //gdk_draw_point (*mask, gc, j, i);
646 gdk_draw_line( *mask, gc, j, i, j + 1, i );
652 GdkColor mask_pattern;
653 mask_pattern.pixel = 1;
654 gdk_gc_set_foreground( gc, &mask_pattern );
655 gdk_draw_rectangle( *mask, gc, 1, 0, 0, width, height );
661 void load_pixmap( const char* filename, GtkWidget* widget, GdkPixmap **gdkpixmap, GdkBitmap **mask ){
664 str = g_strBitmapsPath;
667 bmp_to_pixmap( str.GetBuffer(), gdkpixmap, mask );
668 if ( *gdkpixmap == NULL ) {
669 printf( "gdkpixmap was null\n" );
670 gchar *dummy[] = { "1 1 1 1", " c None", " " };
671 printf( "calling gdk_pixmap_create_from_xpm_d\n" );
672 *gdkpixmap = gdk_pixmap_create_from_xpm_d( gdk_get_default_root_window(), mask, NULL, dummy );
676 // this is the same as above but used by the plugins
677 // GdkPixmap **gdkpixmap, GdkBitmap **mask
678 bool WINAPI load_plugin_bitmap( const char* filename, void **gdkpixmap, void **mask ){
681 str = g_strGameToolsPath;
682 str += g_strPluginsDir;
685 bmp_to_pixmap( str.GetBuffer(), (GdkPixmap **)gdkpixmap, (GdkBitmap **)mask );
687 if ( *gdkpixmap == NULL ) {
688 // look in the core plugins
690 str += g_strPluginsDir;
693 bmp_to_pixmap( str.GetBuffer(), (GdkPixmap **)gdkpixmap, (GdkBitmap **)mask );
695 if ( *gdkpixmap == NULL ) {
697 // look in core modules
699 str += g_strModulesDir;
702 bmp_to_pixmap( str.GetBuffer(), (GdkPixmap **)gdkpixmap, (GdkBitmap **)mask );
704 if ( *gdkpixmap == NULL ) {
705 gchar *dummy[] = { "1 1 1 1", " c None", " " };
706 *gdkpixmap = gdk_pixmap_create_from_xpm_d( gdk_get_default_root_window(), (GdkBitmap **)mask, NULL, dummy );
714 // Load a xpm file and return a pixmap widget.
715 GtkWidget* new_pixmap( GtkWidget* widget, const char* filename ){
716 GdkPixmap *gdkpixmap;
720 load_pixmap( filename, widget, &gdkpixmap, &mask );
721 pixmap = gtk_pixmap_new( gdkpixmap, mask );
723 gdk_drawable_unref( gdkpixmap );
724 gdk_drawable_unref( mask );
729 // =============================================================================
732 GtkWidget* menu_separator( GtkWidget *menu ){
733 GtkWidget *menu_item = gtk_menu_item_new();
734 gtk_menu_append( GTK_MENU( menu ), menu_item );
735 gtk_widget_set_sensitive( menu_item, FALSE );
736 gtk_widget_show( menu_item );
740 GtkWidget* menu_tearoff( GtkWidget *menu ){
741 GtkWidget *menu_item = gtk_tearoff_menu_item_new();
742 gtk_menu_append( GTK_MENU( menu ), menu_item );
743 // gtk_widget_set_sensitive (menu_item, FALSE); -- controls whether menu is detachable
744 gtk_widget_show( menu_item );
748 GtkWidget* create_sub_menu_with_mnemonic( GtkWidget *bar, const gchar *mnemonic ){
749 GtkWidget *item, *sub_menu;
751 item = gtk_menu_item_new_with_mnemonic( mnemonic );
752 gtk_widget_show( item );
753 gtk_container_add( GTK_CONTAINER( bar ), item );
755 sub_menu = gtk_menu_new();
756 gtk_menu_item_set_submenu( GTK_MENU_ITEM( item ), sub_menu );
761 extern void AddMenuItem( GtkWidget* menu, unsigned int id );
763 GtkWidget* create_menu_item_with_mnemonic( GtkWidget *menu, const gchar *mnemonic, GtkSignalFunc func, int id ){
766 item = gtk_menu_item_new_with_mnemonic( mnemonic );
768 gtk_widget_show( item );
769 gtk_container_add( GTK_CONTAINER( menu ), item );
770 gtk_signal_connect( GTK_OBJECT( item ), "activate", GTK_SIGNAL_FUNC( func ), GINT_TO_POINTER( id ) );
772 AddMenuItem( item, id );
776 GtkWidget* create_check_menu_item_with_mnemonic( GtkWidget *menu, const gchar *mnemonic, GtkSignalFunc func, int id, gboolean active ){
779 item = gtk_check_menu_item_new_with_mnemonic( mnemonic );
781 gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( item ), active );
782 gtk_widget_show( item );
783 gtk_container_add( GTK_CONTAINER( menu ), item );
784 gtk_signal_connect( GTK_OBJECT( item ), "activate", GTK_SIGNAL_FUNC( func ), GINT_TO_POINTER( id ) );
786 AddMenuItem( item, id );
790 GtkWidget* create_radio_menu_item_with_mnemonic( GtkWidget *menu, GtkWidget *last, const gchar *mnemonic, GtkSignalFunc func, int id, gboolean state ){
792 GSList *group = (GSList*)NULL;
794 if ( last != NULL ) {
795 group = gtk_radio_menu_item_group( GTK_RADIO_MENU_ITEM( last ) );
797 item = gtk_radio_menu_item_new_with_mnemonic( group, mnemonic );
798 gtk_check_menu_item_set_state( GTK_CHECK_MENU_ITEM( item ), state );
800 gtk_widget_show( item );
801 gtk_container_add( GTK_CONTAINER( menu ), item );
802 gtk_signal_connect( GTK_OBJECT( item ), "activate", GTK_SIGNAL_FUNC( func ), GINT_TO_POINTER( id ) );
804 AddMenuItem( item, id );
808 GtkWidget* create_menu_in_menu_with_mnemonic( GtkWidget *menu, const gchar *mnemonic ){
809 GtkWidget *item, *submenu;
811 item = gtk_menu_item_new_with_mnemonic( mnemonic );
812 gtk_widget_show( item );
813 gtk_container_add( GTK_CONTAINER( menu ), item );
815 submenu = gtk_menu_new();
816 gtk_menu_item_set_submenu( GTK_MENU_ITEM( item ), submenu );
821 // =============================================================================
824 void dialog_button_callback( GtkWidget *widget, gpointer data ) {
828 parent = gtk_widget_get_toplevel( widget );
829 loop = (int*)g_object_get_data( G_OBJECT( parent ), "loop" );
830 ret = (int*)g_object_get_data( G_OBJECT( parent ), "ret" );
833 *ret = GPOINTER_TO_INT( data );
836 gint dialog_delete_callback( GtkWidget *widget, GdkEvent* event, gpointer data ){
839 gtk_widget_hide( widget );
840 loop = (int*)g_object_get_data( G_OBJECT( widget ), "loop" );
846 gint dialog_url_callback( GtkWidget *widget, GdkEvent* event, gpointer data ){
847 OpenURL( (const char *)g_object_get_data( G_OBJECT( widget ), "URL" ) );
852 int WINAPI gtk_MessageBox( void *parent, const char* lpText, const char* lpCaption, guint32 uType, const char* URL ){
853 GtkWidget *window, *w, *vbox, *hbox;
854 GtkAccelGroup *accel;
855 int mode = ( uType & MB_TYPEMASK ), ret, loop = 1;
857 window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
858 gtk_signal_connect( GTK_OBJECT( window ), "delete_event",
859 GTK_SIGNAL_FUNC( dialog_delete_callback ), NULL );
860 gtk_signal_connect( GTK_OBJECT( window ), "destroy",
861 GTK_SIGNAL_FUNC( gtk_widget_destroy ), NULL );
862 gtk_window_set_title( GTK_WINDOW( window ), lpCaption );
863 gtk_container_border_width( GTK_CONTAINER( window ), 10 );
864 g_object_set_data( G_OBJECT( window ), "loop", &loop );
865 g_object_set_data( G_OBJECT( window ), "ret", &ret );
866 gtk_widget_realize( window );
868 gtk_window_set_policy( GTK_WINDOW( window ),FALSE,FALSE,TRUE );
870 if ( parent != NULL ) {
871 gtk_window_set_transient_for( GTK_WINDOW( window ), GTK_WINDOW( parent ) );
874 accel = gtk_accel_group_new();
875 gtk_window_add_accel_group( GTK_WINDOW( window ), accel );
877 vbox = gtk_vbox_new( FALSE, 10 );
878 gtk_container_add( GTK_CONTAINER( window ), vbox );
879 gtk_widget_show( vbox );
881 w = gtk_label_new( lpText );
882 gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 2 );
883 gtk_label_set_justify( GTK_LABEL( w ), GTK_JUSTIFY_LEFT );
884 gtk_widget_show( w );
886 w = gtk_hseparator_new();
887 gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 2 );
888 gtk_widget_show( w );
890 hbox = gtk_hbox_new( FALSE, 10 );
891 gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 2 );
892 gtk_widget_show( hbox );
894 if ( mode == MB_OK ) {
895 w = gtk_button_new_with_label( _( "Ok" ) );
896 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
897 gtk_signal_connect( GTK_OBJECT( w ), "clicked",
898 GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDOK ) );
899 gtk_widget_add_accelerator( w, "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
900 gtk_widget_add_accelerator( w, "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
901 GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT );
902 gtk_widget_grab_default( w );
903 gtk_widget_show( w );
906 else if ( mode == MB_OKCANCEL ) {
907 w = gtk_button_new_with_label( _( "Ok" ) );
908 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
909 gtk_signal_connect( GTK_OBJECT( w ), "clicked",
910 GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDOK ) );
911 gtk_widget_add_accelerator( w, "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
912 GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT );
913 gtk_widget_grab_default( w );
914 gtk_widget_show( w );
916 w = gtk_button_new_with_label( _( "Cancel" ) );
917 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
918 gtk_signal_connect( GTK_OBJECT( w ), "clicked",
919 GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDCANCEL ) );
920 gtk_widget_add_accelerator( w, "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
921 gtk_widget_show( w );
924 else if ( mode == MB_YESNOCANCEL ) {
925 w = gtk_button_new_with_label( _( "Yes" ) );
926 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
927 gtk_signal_connect( GTK_OBJECT( w ), "clicked",
928 GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDYES ) );
929 GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT );
930 gtk_widget_grab_default( w );
931 gtk_widget_show( w );
933 w = gtk_button_new_with_label( _( "No" ) );
934 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
935 gtk_signal_connect( GTK_OBJECT( w ), "clicked",
936 GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDNO ) );
937 gtk_widget_show( w );
939 w = gtk_button_new_with_label( _( "Cancel" ) );
940 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
941 gtk_signal_connect( GTK_OBJECT( w ), "clicked",
942 GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDCANCEL ) );
943 gtk_widget_show( w );
946 else /* if (mode == MB_YESNO) */
948 w = gtk_button_new_with_label( _( "Yes" ) );
949 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
950 gtk_signal_connect( GTK_OBJECT( w ), "clicked",
951 GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDYES ) );
952 GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT );
953 gtk_widget_grab_default( w );
954 gtk_widget_show( w );
956 w = gtk_button_new_with_label( _( "No" ) );
957 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
958 gtk_signal_connect( GTK_OBJECT( w ), "clicked",
959 GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDNO ) );
960 gtk_widget_show( w );
965 w = gtk_button_new_with_label( _( "Go to URL" ) );
966 gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
967 gtk_signal_connect( GTK_OBJECT( w ), "clicked",
968 GTK_SIGNAL_FUNC( dialog_url_callback ), NULL );
969 g_object_set_data( G_OBJECT( w ), "URL", (void *)URL );
970 GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT );
971 gtk_widget_grab_default( w );
972 gtk_widget_show( w );
976 gtk_widget_show( window );
977 gtk_grab_add( window );
980 gtk_main_iteration();
982 gtk_grab_remove( window );
983 gtk_widget_destroy( window );
988 // =============================================================================
991 // fenris #3078 WHENHELLISFROZENOVER
993 static void file_sel_callback( GtkWidget *widget, gpointer data ){
998 parent = gtk_widget_get_toplevel( widget );
999 loop = (int*)g_object_get_data( G_OBJECT( parent ), "loop" );
1000 success = (bool*)g_object_get_data( G_OBJECT( parent ), "success" );
1002 if ( GPOINTER_TO_INT( data ) == IDOK ) {
1010 #include <commdlg.h>
1011 static OPENFILENAME ofn; /* common dialog box structure */
1012 static char szDirName[MAX_PATH]; /* directory string */
1013 static char szFile[MAX_PATH]; /* filename string */
1014 static char szFileTitle[MAX_PATH]; /* file title string */
1015 static int i, cbString; /* integer count variables */
1016 static HANDLE hf; /* file handle */
1018 static char szFile[QER_MAX_NAMELEN];
1021 #define FILEDLG_CUSTOM_FILTER_LENGTH 64
1022 // to be used with the advanced file selector
1024 class CFileType : public IFileTypeList
1026 struct filetype_copy_t
1028 void operator=( const filetype_t& other ){
1029 m_name = other.name;
1030 m_pattern = other.pattern;
1039 m_strWin32Filters = NULL;
1040 m_pstrGTKMasks = NULL;
1043 virtual ~CFileType(){
1045 DestroyWin32Filters();
1049 void addType( filetype_t type ){
1050 filetype_copy_t* newTypes = new filetype_copy_t [m_nTypes + 1];
1051 if ( m_nTypes > 0 ) {
1052 for ( int i = 0; i < m_nTypes; i++ )
1053 newTypes[i] = m_pTypes[i];
1056 m_pTypes = newTypes;
1057 m_pTypes[m_nTypes] = type;
1059 ConstructGTKMasks();
1060 ConstructWin32Filters();
1063 filetype_t GetTypeForWin32Filter( const char *filter ) const {
1064 for ( int i = 0; i < m_nTypes; i++ )
1065 if ( strcmp( m_pTypes[i].m_pattern.c_str(), filter ) == 0 ) {
1066 return filetype_t( m_pTypes[i].m_name.c_str(), m_pTypes[i].m_pattern.c_str() );
1068 return filetype_t();
1071 filetype_t GetTypeForGTKMask( const char *mask ) const {
1072 for ( int i = 0; i < m_nTypes; i++ )
1073 if ( strcmp( m_pstrGTKMasks[i],mask ) == 0 ) {
1074 return filetype_t( m_pTypes[i].m_name.c_str(), m_pTypes[i].m_pattern.c_str() );
1076 return filetype_t();
1083 filetype_t GetTypeForIndex( int index ) const // Zero-based index.
1085 if ( index >= 0 && index < m_nTypes ) {
1086 return filetype_t( m_pTypes[index].m_name.c_str(), m_pTypes[index].m_pattern.c_str() );
1088 return filetype_t();
1091 char *m_strWin32Filters;
1092 char **m_pstrGTKMasks;
1095 filetype_copy_t *m_pTypes;
1097 void DestroyWin32Filters(){
1098 delete[] m_strWin32Filters;
1101 void ConstructWin32Filters(){
1106 DestroyWin32Filters();
1107 for ( i = 0; i < m_nTypes; i++ )
1108 len = len + strlen( m_pTypes[i].m_name.c_str() ) + strlen( m_pTypes[i].m_pattern.c_str() ) * 2 + 5;
1109 m_strWin32Filters = new char[len + 1]; // length + null char
1110 for ( i = 0, w = m_strWin32Filters; i < m_nTypes; i++ )
1112 for ( r = m_pTypes[i].m_name.c_str(); *r != '\0'; r++, w++ )
1116 for ( r = m_pTypes[i].m_pattern.c_str(); *r != '\0'; r++, w++ )
1120 for ( r = m_pTypes[i].m_pattern.c_str(); *r != '\0'; r++, w++ )
1121 *w = ( *r == ',' ) ? ';' : *r;
1124 m_strWin32Filters[len] = '\0';
1127 void DestroyGTKMasks(){
1128 if ( m_pstrGTKMasks != NULL ) {
1129 for ( char **p = m_pstrGTKMasks; *p != NULL; p++ )
1132 delete[] m_pstrGTKMasks;
1135 void ConstructGTKMasks(){
1141 m_pstrGTKMasks = new char*[m_nTypes + 1];
1142 for ( i = 0; i < m_nTypes; i++ )
1144 len = strlen( m_pTypes[i].m_name.c_str() ) + strlen( m_pTypes[i].m_pattern.c_str() ) + 3;
1145 m_pstrGTKMasks[i] = new char[len + 1]; // length + null char
1146 w = m_pstrGTKMasks[i];
1147 for ( r = m_pTypes[i].m_name.c_str(); *r != '\0'; r++, w++ )
1151 for ( r = m_pTypes[i].m_pattern.c_str(); *r != '\0'; r++, w++ )
1156 m_pstrGTKMasks[m_nTypes] = NULL;
1168 } win32_native_file_dialog_comms_t;
1170 DWORD WINAPI win32_native_file_dialog_thread_func( LPVOID lpParam ){
1171 win32_native_file_dialog_comms_t *fileDialogComms;
1172 fileDialogComms = (win32_native_file_dialog_comms_t *) lpParam;
1173 if ( fileDialogComms->open ) {
1174 fileDialogComms->dlgRtnVal = GetOpenFileName( fileDialogComms->ofn );
1177 fileDialogComms->dlgRtnVal = GetSaveFileName( fileDialogComms->ofn );
1179 fileDialogComms->done = true; // No need to synchronize around lock, one-way gate.
1186 * @param[in] baseSubDir should have a trailing slash if not @c NULL
1188 const char* file_dialog( void *parent, gboolean open, const char* title, const char* path, const char* pattern, const char *baseSubDir ){
1191 static bool in_file_dialog = false;
1192 HANDLE fileDialogThreadHandle;
1193 win32_native_file_dialog_comms_t fileDialogComms;
1198 GtkWidget* file_sel;
1199 char *new_path = NULL;
1205 if ( pattern != NULL ) {
1206 GetFileTypeRegistry()->getTypeList( pattern, &typelist );
1210 if ( g_PrefsDlg.m_bNativeGUI ) {
1211 // do that the native way
1213 if ( in_file_dialog ) {
1214 return NULL; // Avoid recursive entry.
1216 in_file_dialog = true;
1217 /* Set the members of the OPENFILENAME structure. */
1218 // See http://msdn.microsoft.com/en-us/library/ms646839%28v=vs.85%29.aspx .
1219 memset( &ofn, 0, sizeof( ofn ) );
1220 ofn.lStructSize = sizeof( ofn );
1221 ofn.hwndOwner = (HWND)GDK_WINDOW_HWND( g_pParentWnd->m_pWidget->window );
1222 ofn.nFilterIndex = 1; // The index is 1-based, not 0-based. This basically says,
1223 // "select the first filter as default".
1225 ofn.lpstrFilter = typelist.m_strWin32Filters;
1229 // TODO: Would be a bit cleaner if we could extract this string from
1230 // GetFileTypeRegistry() instead of hardcoding it here.
1231 ofn.lpstrFilter = "all files\0*.*\0"; // Second '\0' will be added to end of string.
1234 ofn.lpstrFile = szFile;
1235 ofn.nMaxFile = sizeof( szFile );
1237 // szDirName: Radiant uses unix convention for paths internally
1238 // Win32 (of course) and Gtk (who would have thought) expect the '\\' convention
1239 // copy path, replacing dir separators as appropriate
1240 for ( r = path, w = szDirName; *r != '\0'; r++ )
1241 *w++ = ( *r == '/' ) ? '\\' : *r;
1244 ofn.lpstrInitialDir = szDirName;
1246 ofn.lpstrTitle = title;
1247 ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
1249 memset( &fileDialogComms, 0, sizeof( fileDialogComms ) );
1250 fileDialogComms.open = open;
1251 fileDialogComms.ofn = &ofn;
1253 fileDialogThreadHandle =
1254 CreateThread( NULL, // lpThreadAttributes
1255 0, // dwStackSize, default stack size
1256 win32_native_file_dialog_thread_func, // lpStartAddress, funcion to call
1257 &fileDialogComms, // lpParameter, argument to pass to function
1258 0, // dwCreationFlags
1259 NULL ); // lpThreadId
1263 // Avoid blocking indefinitely. Another thread will set fileDialogComms->done to true;
1264 // we don't want to be in an indefinite blocked state when this happens. We want to break
1265 // out of here eventually.
1266 while ( gtk_events_pending() ) {
1267 gtk_main_iteration();
1272 if ( fileDialogComms.done ) {
1273 dialogDone = true; // One more loop of gtk_main_iteration() to get things in sync.
1275 // Avoid tight infinte loop, add a small amount of sleep.
1278 // Make absolutely sure that the thread is finished before we call CloseHandle().
1279 WaitForSingleObject( fileDialogThreadHandle, INFINITE );
1280 CloseHandle( fileDialogThreadHandle );
1282 in_file_dialog = false;
1284 if ( !fileDialogComms.dlgRtnVal ) {
1285 return NULL; // Cancelled.
1288 if ( pattern != NULL ) {
1289 type = typelist.GetTypeForIndex( ofn.nFilterIndex - 1 );
1297 // do that the Gtk way
1298 if ( title == NULL ) {
1299 title = open ? _( "Open File" ) : _( "Save File" );
1302 // we expect an actual path below, if the path is NULL we might crash
1303 if ( !path || path[0] == '\0' ) {
1304 strcpy( buf, g_pGameDescription->mEnginePath.GetBuffer() );
1305 strcat( buf, g_pGameDescription->mBaseGame.GetBuffer() );
1308 strcat( buf, baseSubDir );
1313 // alloc new path with extra char for dir separator
1314 new_path = new char[strlen( path ) + 1 + 1];
1315 // copy path, replacing dir separators as appropriate
1316 for ( r = path, w = new_path; *r != '\0'; r++ )
1317 *w++ = ( *r == '/' ) ? G_DIR_SEPARATOR : *r;
1318 // add dir separator to end of path if required
1319 if ( *( w - 1 ) != G_DIR_SEPARATOR ) {
1320 *w++ = G_DIR_SEPARATOR;
1325 file_sel = gtk_file_chooser_dialog_new( title,
1326 GTK_WINDOW( parent ),
1327 open ? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE,
1329 GTK_RESPONSE_CANCEL,
1330 open ? GTK_STOCK_OPEN : GTK_STOCK_SAVE,
1331 GTK_RESPONSE_ACCEPT,
1333 gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( file_sel ), new_path );
1336 // Setting the file chooser dialog to modal and centering it on the parent is done automatically.
1338 if ( pattern != NULL ) {
1339 for ( int i = 0; i < typelist.GetNumTypes(); i++ ) {
1340 GtkFileFilter *filter = gtk_file_filter_new();
1341 type = typelist.GetTypeForIndex( i );
1342 // We can use type.name here, or m_pstrGTKMasks[i], which includes the actual pattern.
1343 gtk_file_filter_set_name( filter, typelist.m_pstrGTKMasks[i] );
1344 gtk_file_filter_add_pattern( filter, type.pattern );
1345 // "Note that the chooser takes ownership of the filter, so
1346 // you have to ref and sink it if you want to keep a reference."
1347 // So I guess we won't need to garbage collect this.
1348 gtk_file_chooser_add_filter( GTK_FILE_CHOOSER( file_sel ), filter );
1352 if ( gtk_dialog_run( GTK_DIALOG( file_sel ) ) == GTK_RESPONSE_ACCEPT ) {
1353 strcpy( szFile, gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( file_sel ) ) );
1359 if ( pattern != NULL ) {
1360 GtkFileFilter *filter = gtk_file_chooser_get_filter( GTK_FILE_CHOOSER( file_sel ) );
1361 if ( filter == NULL ) {
1362 type = filetype_t();
1365 type = typelist.GetTypeForGTKMask( gtk_file_filter_get_name( filter ) );
1368 gtk_widget_destroy( file_sel );
1374 // don't return an empty filename
1375 if ( szFile[0] == '\0' ) {
1379 // convert back to unix format
1380 for ( w = szFile; *w != '\0'; w++ )
1385 /* \todo SPoG - file_dialog should return filetype information separately.. not force file extension.. */
1386 if ( !open && pattern != NULL ) {
1387 v = strrchr( szFile, '/' );
1388 w = strrchr( szFile, '.' );
1389 if ( ( v && w && w < v ) || // Last '.' is before the file.
1390 w == NULL ) { // Extension missing.
1391 if ( type.pattern[0] ) {
1392 w = szFile + strlen( szFile );
1393 strcpy( w, type.pattern + 1 ); // Add extension of selected filter type.
1396 // type will be empty if for example there were no filters for pattern,
1397 // or if some other UI inconsistencies happen.
1398 if ( gtk_MessageBox( parent, "No file extension specified in file to be saved.\nAttempt to save anyways?",
1399 "GtkRadiant", MB_YESNO ) == IDNO ) {
1404 else { // An extension was explicitly in the filename.
1405 bool knownExtension = false;
1406 for ( int i = typelist.GetNumTypes() - 1; i >= 0; i-- ) {
1407 type = typelist.GetTypeForIndex( i );
1408 if ( type.pattern[0] && strcmp( w, type.pattern + 1 ) == 0 ) {
1409 knownExtension = true;
1413 if ( !knownExtension ) {
1414 if ( gtk_MessageBox( parent, "Unknown file extension for this save operation.\nAttempt to save anyways?",
1415 "GtkRadiant", MB_YESNO ) == IDNO ) {
1422 // prompt to overwrite existing files
1424 if ( access( szFile, R_OK ) == 0 ) {
1425 if ( gtk_MessageBox( parent, "File already exists.\nOverwrite?", "GtkRadiant", MB_YESNO ) == IDNO ) {
1434 char* WINAPI dir_dialog( void *parent, const char* title, const char* path ){
1435 GtkWidget* file_sel;
1436 char* filename = (char*)NULL;
1438 bool success = false;
1440 file_sel = gtk_file_selection_new( title );
1441 gtk_signal_connect( GTK_OBJECT( GTK_FILE_SELECTION( file_sel )->ok_button ), "clicked",
1442 GTK_SIGNAL_FUNC( file_sel_callback ), GINT_TO_POINTER( IDOK ) );
1443 gtk_signal_connect( GTK_OBJECT( GTK_FILE_SELECTION( file_sel )->cancel_button ), "clicked",
1444 GTK_SIGNAL_FUNC( file_sel_callback ), GINT_TO_POINTER( IDCANCEL ) );
1445 gtk_signal_connect( GTK_OBJECT( file_sel ), "delete_event",
1446 GTK_SIGNAL_FUNC( dialog_delete_callback ), NULL );
1447 gtk_file_selection_hide_fileop_buttons( GTK_FILE_SELECTION( file_sel ) );
1449 if ( parent != NULL ) {
1450 gtk_window_set_transient_for( GTK_WINDOW( file_sel ), GTK_WINDOW( parent ) );
1453 gtk_widget_hide( GTK_FILE_SELECTION( file_sel )->file_list->parent );
1455 g_object_set_data( G_OBJECT( file_sel ), "loop", &loop );
1456 g_object_set_data( G_OBJECT( file_sel ), "success", &success );
1458 if ( path != NULL ) {
1459 gtk_file_selection_set_filename( GTK_FILE_SELECTION( file_sel ), path );
1462 gtk_grab_add( file_sel );
1463 gtk_widget_show( file_sel );
1466 gtk_main_iteration();
1468 filename = g_strdup( gtk_file_selection_get_filename( GTK_FILE_SELECTION( file_sel ) ) );
1470 gtk_grab_remove( file_sel );
1471 gtk_widget_destroy( file_sel );
1476 bool WINAPI color_dialog( void *parent, float *color, const char* title ){
1479 int loop = 1, ret = IDCANCEL;
1485 dlg = gtk_color_selection_dialog_new( title );
1486 gtk_color_selection_set_color( GTK_COLOR_SELECTION( GTK_COLOR_SELECTION_DIALOG( dlg )->colorsel ), clr );
1487 gtk_signal_connect( GTK_OBJECT( dlg ), "delete_event",
1488 GTK_SIGNAL_FUNC( dialog_delete_callback ), NULL );
1489 gtk_signal_connect( GTK_OBJECT( dlg ), "destroy",
1490 GTK_SIGNAL_FUNC( gtk_widget_destroy ), NULL );
1491 gtk_signal_connect( GTK_OBJECT( GTK_COLOR_SELECTION_DIALOG( dlg )->ok_button ), "clicked",
1492 GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDOK ) );
1493 gtk_signal_connect( GTK_OBJECT( GTK_COLOR_SELECTION_DIALOG( dlg )->cancel_button ), "clicked",
1494 GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDCANCEL ) );
1495 g_object_set_data( G_OBJECT( dlg ), "loop", &loop );
1496 g_object_set_data( G_OBJECT( dlg ), "ret", &ret );
1498 if ( parent != NULL ) {
1499 gtk_window_set_transient_for( GTK_WINDOW( dlg ), GTK_WINDOW( parent ) );
1502 gtk_widget_show( dlg );
1503 gtk_grab_add( dlg );
1506 gtk_main_iteration();
1509 gtk_color_selection_get_current_color( GTK_COLOR_SELECTION( GTK_COLOR_SELECTION_DIALOG( dlg )->colorsel ), &gdkcolor );
1510 clr[0] = gdkcolor.red / 65535.0;
1511 clr[1] = gdkcolor.green / 65535.0;
1512 clr[2] = gdkcolor.blue / 65535.0;
1514 gtk_grab_remove( dlg );
1515 gtk_widget_destroy( dlg );
1517 if ( ret == IDOK ) {
1518 color[0] = (float)clr[0];
1519 color[1] = (float)clr[1];
1520 color[2] = (float)clr[2];
1528 void OpenURL( const char *url ){
1529 // let's put a little comment
1530 Sys_Printf( "OpenURL: %s\n", url );
1532 // \todo FIXME: the way we open URLs on *nix should be improved. A script is good (see how I do on RTCW)
1533 char command[2 * PATH_MAX];
1534 snprintf( command, sizeof( command ), "%s/openurl.sh \"%s\" &", g_strAppPath.GetBuffer(), url );
1535 if ( system( command ) != 0 ) {
1536 gtk_MessageBox( g_pParentWnd->m_pWidget, "Failed to launch Netscape!" );
1540 char command[2 * PATH_MAX];
1541 snprintf( command, sizeof( command ),
1542 "open \"%s\" &", url, url );
1543 if ( system( command ) != 0 ) {
1544 gtk_MessageBox( g_pParentWnd->m_pWidget, "Unable to launch browser!" );
1548 ShellExecute( (HWND)GDK_WINDOW_HWND( g_pParentWnd->m_pWidget->window ), "open", url, NULL, NULL, SW_SHOW );
1552 void CheckMenuSplitting( GtkWidget *&menu ){
1553 GtkWidget *item,*menu2;
1555 GtkRequisition requisition;
1558 gtk_widget_size_request( GTK_WIDGET( menu ), &requisition );
1559 screen_height = gdk_screen_height();
1561 if ( ( screen_height - requisition.height ) < 20 ) {
1562 menu2 = gtk_menu_new();
1564 // move the last 2 items to a submenu (3 because of win32)
1565 for ( int i = 0; i < 3; i++ )
1567 item = GTK_WIDGET( g_list_last( gtk_container_children( GTK_CONTAINER( menu ) ) )->data );
1568 gtk_widget_ref( item );
1569 gtk_container_remove( GTK_CONTAINER( menu ), item );
1570 gtk_menu_append( GTK_MENU( menu2 ), item );
1571 gtk_widget_unref( item );
1574 item = gtk_menu_item_new_with_label( "--------" );
1575 gtk_widget_show( item );
1576 gtk_container_add( GTK_CONTAINER( menu ), item );
1577 gtk_menu_item_set_submenu( GTK_MENU_ITEM( item ), menu2 );