/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ /* * Copyright 2001-2008 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef PLATFORM_GTK #include #include "linuxutl.h" #include "gtknewgame.h" #include "strutils.h" #include "nwgamest.h" #include "gtkconnsdlg.h" #include "gtkutils.h" #define MAX_SIZE_CHOICES 10 typedef struct GtkNewGameState { GtkAppGlobals* globals; NewGameCtx* newGameCtxt; CommsAddrRec addr; DeviceRole role; gboolean revert; gboolean cancelled; XP_Bool loaded; gboolean isNewGame; short nCols; /* for board size */ #ifndef XWFEATURE_STANDALONE_ONLY GtkWidget* remoteChecks[MAX_NUM_PLAYERS]; GtkWidget* radios[3]; #endif GtkWidget* robotChecks[MAX_NUM_PLAYERS]; GtkWidget* nameLabels[MAX_NUM_PLAYERS]; GtkWidget* nameFields[MAX_NUM_PLAYERS]; GtkWidget* passwdLabels[MAX_NUM_PLAYERS]; GtkWidget* passwdFields[MAX_NUM_PLAYERS]; GtkWidget* nPlayersCombo; GtkWidget* nPlayersLabel; GtkWidget* juggleButton; } GtkNewGameState; static void nplayers_menu_changed( GtkComboBox* combo, GtkNewGameState* state ) { gint index = gtk_combo_box_get_active( GTK_COMBO_BOX(combo) ); if ( index >= 0 ) { NGValue value = { .ng_u16 = index + 1 }; newg_attrChanged( state->newGameCtxt, NG_ATTR_NPLAYERS, value ); } } /* nplayers_menu_changed */ #ifndef XWFEATURE_STANDALONE_ONLY static void radio_clicked( GtkRadioButton* radio, gpointer gp ) { GtkNewGameState* state = (GtkNewGameState*)gp; gint index = -1, ii; for ( ii = 0; ii < VSIZE(state->radios); ++ii ) { if (GTK_RADIO_BUTTON(state->radios[ii]) == radio ) { index = ii; break; } } if ( index >= 0 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio)) ) { DeviceRole role = (DeviceRole)index; NGValue value = { .ng_role = role }; newg_attrChanged( state->newGameCtxt, NG_ATTR_ROLE, value ); #if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY if ( state->loaded && SERVER_STANDALONE != role ) { gtkConnsDlg( state->globals, &state->addr, FALSE ); } #endif } } /* role_combo_changed */ #endif static void callChangedWithIndex( GtkNewGameState* state, GtkWidget* item, GtkWidget** items ) { NGValue value; gint player; for ( player = 0; player < MAX_NUM_PLAYERS; ++player ) { if ( item == items[player] ) { break; } } XP_ASSERT( player < MAX_NUM_PLAYERS ); value.ng_bool = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(item) ); newg_colChanged( state->newGameCtxt, player ); } /* callChangedWithIndex */ static void handle_robot_toggled( GtkWidget* item, GtkNewGameState* state ) { callChangedWithIndex( state, item, state->robotChecks ); } static void handle_juggle( GtkWidget* XP_UNUSED(item), GtkNewGameState* state ) { while ( !newg_juggle( state->newGameCtxt ) ) { } } #ifndef XWFEATURE_STANDALONE_ONLY static void handle_remote_toggled( GtkWidget* item, GtkNewGameState* state ) { callChangedWithIndex( state, item, state->remoteChecks ); } #endif static void size_combo_changed( GtkComboBox* combo, gpointer gp ) { GtkNewGameState* state = (GtkNewGameState*)gp; gint index = gtk_combo_box_get_active( GTK_COMBO_BOX(combo) ); if ( index >= 0 ) { state->nCols = MAX_COLS - index; XP_LOGF( "set nCols = %d", state->nCols ); } } /* size_combo_changed */ static void handle_ok( GtkWidget* XP_UNUSED(widget), gpointer closure ) { GtkNewGameState* state = (GtkNewGameState*)closure; state->cancelled = XP_FALSE; gtk_main_quit(); } /* handle_ok */ static void handle_cancel( GtkWidget* XP_UNUSED(widget), void* closure ) { GtkNewGameState* state = (GtkNewGameState*)closure; state->cancelled = XP_TRUE; gtk_main_quit(); } static void handle_revert( GtkWidget* XP_UNUSED(widget), void* closure ) { GtkNewGameState* state = (GtkNewGameState*)closure; state->revert = TRUE; gtk_main_quit(); } /* handle_revert */ static GtkWidget* makeNewGameDialog( GtkNewGameState* state ) { GtkWidget* dialog; GtkWidget* vbox; GtkWidget* hbox; #ifndef XWFEATURE_STANDALONE_ONLY char* roles[] = { "Standalone", "Host", "Guest" }; #endif GtkWidget* nPlayersCombo; GtkWidget* boardSizeCombo; CurGameInfo* gi; short ii; dialog = gtk_dialog_new(); gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE ); vbox = gtk_vbox_new( FALSE, 0 ); #ifndef XWFEATURE_STANDALONE_ONLY hbox = gtk_hbox_new( FALSE, 0 ); gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Role:"), FALSE, TRUE, 0 ); GtkWidget* radio = NULL; for ( ii = 0; ii < VSIZE(roles); ++ii ) { if ( NULL == radio ) { radio = gtk_radio_button_new_with_label( NULL, roles[ii] ); } else { radio = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radio), roles[ii] ); } state->radios[ii] = radio; g_signal_connect( GTK_OBJECT(radio), "clicked", G_CALLBACK(radio_clicked), state ); gtk_box_pack_start( GTK_BOX(hbox), radio, FALSE, TRUE, 0 ); if ( !state->isNewGame && ii != state->role ) { gtk_widget_set_sensitive( radio, FALSE ); } } gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); #endif /* NPlayers menu */ hbox = gtk_hbox_new( FALSE, 0 ); state->nPlayersLabel = gtk_label_new(""); gtk_box_pack_start( GTK_BOX(hbox), state->nPlayersLabel, FALSE, TRUE, 0 ); nPlayersCombo = gtk_combo_box_new_text(); state->nPlayersCombo = nPlayersCombo; gi = &state->globals->cGlobals.params->gi; for ( ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) { char buf[2] = { ii + '1', '\0' }; gtk_combo_box_append_text( GTK_COMBO_BOX(nPlayersCombo), buf ); } gtk_widget_show( nPlayersCombo ); gtk_box_pack_start( GTK_BOX(hbox), nPlayersCombo, FALSE, TRUE, 0 ); g_signal_connect( GTK_OBJECT(nPlayersCombo), "changed", G_CALLBACK(nplayers_menu_changed), state ); state->juggleButton = makeButton( "Juggle", GTK_SIGNAL_FUNC(handle_juggle), state ); gtk_box_pack_start( GTK_BOX(hbox), state->juggleButton, FALSE, TRUE, 0 ); gtk_widget_show( hbox ); gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); for ( ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) { GtkWidget* label = gtk_label_new("Name:"); #ifndef XWFEATURE_STANDALONE_ONLY GtkWidget* remoteCheck = gtk_check_button_new_with_label( "Remote" ); #endif GtkWidget* nameField = gtk_entry_new(); GtkWidget* passwdField = gtk_entry_new_with_max_length( 6 ); GtkWidget* robotCheck = gtk_check_button_new_with_label( "Robot" ); #ifndef XWFEATURE_STANDALONE_ONLY g_signal_connect( GTK_OBJECT(remoteCheck), "toggled", GTK_SIGNAL_FUNC(handle_remote_toggled), state ); #endif g_signal_connect( GTK_OBJECT(robotCheck), "toggled", GTK_SIGNAL_FUNC(handle_robot_toggled), state ); hbox = gtk_hbox_new( FALSE, 0 ); #ifndef XWFEATURE_STANDALONE_ONLY gtk_box_pack_start( GTK_BOX(hbox), remoteCheck, FALSE, TRUE, 0 ); gtk_widget_show( remoteCheck ); state->remoteChecks[ii] = remoteCheck; #endif gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 0 ); gtk_widget_show( label ); state->nameLabels[ii] = label; gtk_box_pack_start( GTK_BOX(hbox), nameField, FALSE, TRUE, 0 ); gtk_widget_show( nameField ); state->nameFields[ii] = nameField; gtk_box_pack_start( GTK_BOX(hbox), robotCheck, FALSE, TRUE, 0 ); gtk_widget_show( robotCheck ); state->robotChecks[ii] = robotCheck; label = gtk_label_new("Passwd:"); gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 0 ); gtk_widget_show( label ); state->passwdLabels[ii] = label; gtk_box_pack_start( GTK_BOX(hbox), passwdField, FALSE, TRUE, 0 ); gtk_widget_show( passwdField ); state->passwdFields[ii] = passwdField; gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); gtk_widget_show( hbox ); } /* board size choices */ hbox = gtk_hbox_new( FALSE, 0 ); gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Board size"), FALSE, TRUE, 0 ); boardSizeCombo = gtk_combo_box_new_text(); if ( !state->isNewGame ) { gtk_widget_set_sensitive( boardSizeCombo, FALSE ); } for ( ii = 0; ii < MAX_SIZE_CHOICES; ++ii ) { char buf[10]; XP_U16 siz = MAX_COLS - ii; snprintf( buf, sizeof(buf), "%dx%d", siz, siz ); gtk_combo_box_append_text( GTK_COMBO_BOX(boardSizeCombo), buf ); if ( siz == state->nCols ) { gtk_combo_box_set_active( GTK_COMBO_BOX(boardSizeCombo), ii ); } } g_signal_connect( GTK_OBJECT(boardSizeCombo), "changed", G_CALLBACK(size_combo_changed), state ); gtk_widget_show( boardSizeCombo ); gtk_box_pack_start( GTK_BOX(hbox), boardSizeCombo, FALSE, TRUE, 0 ); gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Dictionary: "), FALSE, TRUE, 0 ); if ( !!gi->dictName ) { gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new(gi->dictName), FALSE, TRUE, 0 ); } gtk_widget_show( hbox ); gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); /* buttons at the bottom */ hbox = gtk_hbox_new( FALSE, 0 ); gtk_box_pack_start( GTK_BOX(hbox), makeButton( "Ok", GTK_SIGNAL_FUNC(handle_ok) , state ), FALSE, TRUE, 0 ); if ( state->isNewGame ) { gtk_box_pack_start( GTK_BOX(hbox), makeButton( "Revert", GTK_SIGNAL_FUNC(handle_revert), state ), FALSE, TRUE, 0 ); gtk_box_pack_start( GTK_BOX(hbox), makeButton( "Cancel", GTK_SIGNAL_FUNC(handle_cancel), state ), FALSE, TRUE, 0 ); } gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); gtk_widget_show( vbox ); gtk_container_add( GTK_CONTAINER( GTK_DIALOG(dialog)->action_area), vbox); gtk_widget_show_all (dialog); return dialog; } /* makeNewGameDialog */ static GtkWidget* widgetForCol( const GtkNewGameState* state, XP_U16 player, NewGameColumn col ) { GtkWidget* widget = NULL; if ( col == NG_COL_NAME ) { widget = state->nameFields[player]; } else if ( col == NG_COL_PASSWD ) { widget = state->passwdFields[player]; #ifndef XWFEATURE_STANDALONE_ONLY } else if ( col == NG_COL_REMOTE ) { widget = state->remoteChecks[player]; #endif } else if ( col == NG_COL_ROBOT ) { widget = state->robotChecks[player]; } XP_ASSERT( !!widget ); return widget; } /* widgetForCol */ static GtkWidget* labelForCol( const GtkNewGameState* state, XP_U16 player, NewGameColumn col ) { GtkWidget* widget = NULL; if ( col == NG_COL_NAME ) { widget = state->nameLabels[player]; } else if ( col == NG_COL_PASSWD ) { widget = state->passwdLabels[player]; } return widget; } /* labelForCol */ static void gtk_newgame_col_enable( void* closure, XP_U16 player, NewGameColumn col, XP_TriEnable enable ) { GtkNewGameState* state = (GtkNewGameState*)closure; GtkWidget* widget = widgetForCol( state, player, col ); GtkWidget* label = labelForCol( state, player, col ); gtk_widget_show( widget ); gtk_widget_set_sensitive( widget, enable == TRI_ENAB_ENABLED ); if ( !!label ) { gtk_widget_show( label ); gtk_widget_set_sensitive( label, enable == TRI_ENAB_ENABLED ); } } /* gtk_newgame_col_enable */ static void gtk_newgame_attr_enable( void* closure, NewGameAttr attr, XP_TriEnable enable ) { GtkNewGameState* state = (GtkNewGameState*)closure; GtkWidget* widget = NULL; if ( attr == NG_ATTR_NPLAYERS ) { widget = state->nPlayersCombo; #ifndef XWFEATURE_STANDALONE_ONLY } else if ( attr == NG_ATTR_ROLE ) { /* NG_ATTR_ROLE always enabled */ /* widget = state->roleCombo; */ #endif } else if ( attr == NG_ATTR_CANJUGGLE ) { widget = state->juggleButton; } if ( !!widget ) { gtk_widget_set_sensitive( widget, enable == TRI_ENAB_ENABLED ); } } static void gtk_newgame_col_set( void* closure, XP_U16 player, NewGameColumn col, NGValue value ) { GtkNewGameState* state = (GtkNewGameState*)closure; GtkWidget* widget = widgetForCol( state, player, col ); const XP_UCHAR* cp; switch ( col ) { case NG_COL_NAME: case NG_COL_PASSWD: cp = value.ng_cp? value.ng_cp : ""; gtk_entry_set_text( GTK_ENTRY(widget), cp ); break; #ifndef XWFEATURE_STANDALONE_ONLY case NG_COL_REMOTE: #endif case NG_COL_ROBOT: gtk_toggle_button_set_state( GTK_TOGGLE_BUTTON(widget), value.ng_bool ); break; } } /* gtk_newgame_set */ static void gtk_newgame_col_get( void* closure, XP_U16 player, NewGameColumn col, NgCpCallbk cpcb, const void* cbClosure ) { GtkNewGameState* state = (GtkNewGameState*)closure; NGValue value; GtkWidget* widget = widgetForCol( state, player, col ); switch ( col ) { #ifndef XWFEATURE_STANDALONE_ONLY case NG_COL_REMOTE: #endif case NG_COL_ROBOT: value.ng_bool = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(widget)); break; case NG_COL_PASSWD: case NG_COL_NAME: value.ng_cp = gtk_entry_get_text( GTK_ENTRY(widget) ); break; } (*cpcb)( value, cbClosure ); } /* gtk_newgame_col_get */ static void gtk_newgame_attr_set( void* closure, NewGameAttr attr, NGValue value ) { GtkNewGameState* state = (GtkNewGameState*)closure; if ( attr == NG_ATTR_NPLAYERS ) { XP_U16 ii = value.ng_u16; XP_LOGF( "%s: setting menu %d", __func__, ii-1 ); gtk_combo_box_set_active( GTK_COMBO_BOX(state->nPlayersCombo), ii-1 ); #ifndef XWFEATURE_STANDALONE_ONLY } else if ( attr == NG_ATTR_ROLE ) { gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(state->radios[value.ng_role]), TRUE ); } else if ( attr == NG_ATTR_REMHEADER ) { /* ignored on GTK: no headers at all */ #endif } else if ( attr == NG_ATTR_NPLAYHEADER ) { gtk_label_set_text( GTK_LABEL(state->nPlayersLabel), value.ng_cp ); } } gboolean newGameDialog( GtkAppGlobals* globals, CommsAddrRec* addr, XP_Bool isNewGame ) { GtkNewGameState state; XP_MEMSET( &state, 0, sizeof(state) ); state.globals = globals; state.newGameCtxt = newg_make( MPPARM(globals->cGlobals.params ->util->mpool) isNewGame, globals->cGlobals.params->util, gtk_newgame_col_enable, gtk_newgame_attr_enable, gtk_newgame_col_get, gtk_newgame_col_set, gtk_newgame_attr_set, &state ); state.isNewGame = isNewGame; /* returns when button handler calls gtk_main_quit */ do { GtkWidget* dialog; state.revert = FALSE; state.loaded = XP_FALSE; state.nCols = globals->cGlobals.params->gi.boardSize; state.role = globals->cGlobals.params->gi.serverRole; XP_MEMCPY( &state.addr, addr, sizeof(state.addr) ); dialog = makeNewGameDialog( &state ); newg_load( state.newGameCtxt, &globals->cGlobals.params->gi ); state.loaded = XP_TRUE; gtk_main(); if ( !state.cancelled && !state.revert ) { if ( newg_store( state.newGameCtxt, &globals->cGlobals.params->gi, XP_TRUE ) ) { globals->cGlobals.params->gi.boardSize = state.nCols; } else { /* Do it again if we warned user of inconsistency. */ state.revert = XP_TRUE; } } gtk_widget_destroy( dialog ); } while ( state.revert ); newg_destroy( state.newGameCtxt ); if ( !state.cancelled ) { XP_MEMCPY( addr, &state.addr, sizeof(state.addr) ); } return !state.cancelled; } /* newGameDialog */ #endif /* PLATFORM_GTK */