Merge branch 'android_branch' into fix_dlgdelegate

This commit is contained in:
Eric House 2012-12-31 06:25:49 -08:00
commit d1941090c5
35 changed files with 1260 additions and 281 deletions

View file

@ -25,16 +25,12 @@
android:layout_height="wrap_content"
>
<Button android:id="@+id/exchange_commit"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/button_trade_commit"
style="@style/spaced_buttons"
/>
<Button android:id="@+id/exchange_cancel"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/button_trade_cancel"
style="@style/spaced_buttons"
/>
</LinearLayout>

View file

@ -26,9 +26,7 @@
>
<Button android:id="@+id/button_rescan"
android:text="@string/bt_pick_rescan_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/spaced_buttons"
/>
<!-- <Button android:id="@+id/button_reconfigure" -->
<!-- android:text="@string/bt_pick_reconfig_button" -->
@ -38,9 +36,7 @@
<!-- /> -->
<Button android:id="@+id/button_clear"
android:text="@string/bt_pick_clear_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/spaced_buttons"
/>
</LinearLayout>

View file

@ -4,9 +4,7 @@
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingLeft="8dp"
android:paddingRight="8dp">
>
<LinearLayout android:id="@+id/empty_games_list"
android:orientation="vertical"
android:layout_width="fill_parent"
@ -18,21 +16,25 @@
android:textAppearance="?android:attr/textAppearanceMedium"
android:background="#FF202020"
android:gravity="center"
android:paddingLeft="8dp"
android:paddingRight="8dp"
/>
<TextView android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/empty_games_list2"
android:background="#FF202020"
android:gravity="center"
android:paddingLeft="8dp"
android:paddingRight="8dp"
/>
</LinearLayout>
<ListView android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:drawSelectorOnTop="false"
/>
<ExpandableListView android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:drawSelectorOnTop="false"
/>
<TextView android:id="@+id/empty_list_msg"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
@ -41,11 +43,21 @@
android:text="@string/empty_list_msg"
/>
<Button android:id="@+id/new_game"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="@string/button_new_game"
/>
<LinearLayout android:id="@+id/new_buttons"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp"
>
<Button android:id="@+id/new_game"
android:text="@string/button_new_game"
style="@style/spaced_buttons"
/>
<Button android:id="@+id/new_group"
android:text="@string/button_new_group"
style="@style/spaced_buttons"
/>
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<org.eehouse.android.xw4.GameListGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/game_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:textStyle="italic"
android:background="#FF7F7F7F"
/>

View file

@ -12,6 +12,7 @@
android:focusable="true"
android:clickable="true"
android:background="@android:drawable/list_selector_background"
android:paddingLeft="12dp"
>
<TextView android:id="@+id/view_unloaded"

View file

@ -51,16 +51,12 @@
android:layout_height="wrap_content"
>
<Button android:id="@+id/newgame_local"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_local"
style="@style/spaced_buttons"
/>
<Button android:id="@+id/newgame_local_config"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_local_config"
style="@style/spaced_buttons"
/>
</LinearLayout>
@ -93,17 +89,13 @@
android:layout_height="wrap_content"
>
<Button android:id="@+id/newgame_invite"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_invite"
style="@style/spaced_buttons"
/>
<Button android:id="@+id/newgame_net_config"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_net_config"
style="@style/spaced_buttons"
/>
</LinearLayout>
@ -160,17 +152,13 @@
android:layout_height="wrap_content"
>
<Button android:id="@+id/newgame_invite_sms"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_invite"
style="@style/spaced_buttons"
/>
<Button android:id="@+id/newgame_sms_config"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_net_config"
style="@style/spaced_buttons"
/>
</LinearLayout>
</LinearLayout>
@ -228,17 +216,13 @@
android:layout_height="wrap_content"
>
<Button android:id="@+id/newgame_invite_bt"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_invite"
style="@style/spaced_buttons"
/>
<Button android:id="@+id/newgame_bt_config"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_net_config"
style="@style/spaced_buttons"
/>
</LinearLayout>
</LinearLayout>

View file

@ -20,16 +20,12 @@
android:layout_weight="0"
>
<Button android:id="@+id/revert_colors"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/button_revert_colors"
style="@style/spaced_buttons"
/>
<Button android:id="@+id/revert_all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/button_revert_all"
style="@style/spaced_buttons"
/>
</LinearLayout>
</LinearLayout>

View file

@ -41,9 +41,7 @@
>
<Button android:id="@+id/button_add"
android:text="@string/button_sms_add"
android:layout_height="fill_parent"
android:layout_width="wrap_content"
android:layout_weight="1"
style="@style/spaced_buttons"
/>
<ImageButton android:id="@+id/manual_add_button"
android:layout_height="fill_parent"
@ -53,9 +51,7 @@
/>
<Button android:id="@+id/button_clear"
android:text="@string/bt_pick_clear_button"
android:layout_height="fill_parent"
android:layout_width="wrap_content"
android:layout_weight="1"
style="@style/spaced_buttons"
/>
</LinearLayout>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/list_group_default"
android:title="@string/list_group_default"
/>
<item android:id="@+id/list_group_rename"
android:title="@string/list_group_rename"
/>
<item android:id="@+id/list_group_moveup"
android:title="@string/list_group_moveup"
/>
<item android:id="@+id/list_group_movedown"
android:title="@string/list_group_movedown"
/>
<item android:id="@+id/list_group_delete"
android:title="@string/list_group_delete"
/>
</menu>

View file

@ -7,6 +7,9 @@
<item android:id="@+id/list_item_rename"
android:title="@string/list_item_rename"
/>
<item android:id="@+id/list_item_move"
android:title="@string/list_item_move"
/>
<item android:id="@+id/list_item_delete"
android:title="@string/list_item_delete"
/>

View file

@ -5,6 +5,10 @@
android:title="@string/button_new_game"
android:icon="@android:drawable/ic_menu_add"
/>
<item android:id="@+id/gamel_menu_newgroup"
android:title="@string/button_new_group"
android:icon="@android:drawable/ic_menu_add"
/>
<item android:id="@+id/gamel_menu_prefs"
android:title="@string/menu_prefs"
android:icon="@android:drawable/ic_menu_preferences"
@ -23,10 +27,6 @@
android:icon="@android:drawable/ic_menu_send"
/>
<item android:id="@+id/gamel_menu_delete_all"
android:title="@string/gamel_menu_delete_all"
android:icon="@android:drawable/ic_menu_delete"
/>
<item android:id="@+id/gamel_menu_checkmoves"
android:title="@string/gamel_menu_checkmoves"
android:icon="@drawable/stat_notify_sync"

View file

@ -9,7 +9,7 @@
<h3>New with this release</h3>
<ul>
<li>Allow grouping of games in collapsible user-defined categores:
<li>Allow grouping of games in collapsible user-defined categories:
"Games with Kati", "Finished games", etc.</li>
</ul>

View file

@ -72,6 +72,7 @@
<string name="key_relay_regid">key_relay_regid</string>
<string name="key_checked_sms">key_checked_sms</string>
<string name="key_default_group">key_default_group</string>
<string name="key_group_posns">key_group_posns</string>
<string name="key_notagain_sync">key_notagain_sync</string>
<string name="key_notagain_chat">key_notagain_chat</string>

View file

@ -27,6 +27,7 @@
menuitem in main games-list screen's menu. (The botton can
be hidden in the same way as the above text.) -->
<string name="button_new_game">Add game</string>
<string name="button_new_group">Add group</string>
<!-- When the game list is empty and the above messages and button
are hidden via preferences, this text is shown -->
@ -142,6 +143,9 @@
<string name="list_item_config">Game settings...</string>
<!-- pulls up dialog to rename (change name of) the selected game -->
<string name="list_item_rename">Rename...</string>
<!-- pulls up dialog to change the group of the selected game -->
<string name="list_item_move">Move to group...</string>
<!-- pulls up dialog to delete the selected game -->
<string name="list_item_delete">Delete</string>
<!-- pulls up dialog to reset the selected game, that is to remove
@ -181,12 +185,6 @@
game? Resetting erases all moves and any connection
information.</string>
<!-- Text of confirmation dialog posted when gamel_menu_delete_all
button is pressed -->
<string name="confirm_delete_all">Are you sure you want to delete
all games?</string>
<!--
############################################################
# :Screens:
@ -630,7 +628,8 @@
<!-- Appended to the above in the phonies_warn case. User may
ignore the warning -->
<string name="badwords_accept"> Do you still want to accept this move?</string>
<string name="badwords_accept">\u0020Do you still want to accept
this move?</string>
<!-- Appended to the above in the phonies_disallow case. User has
lost his turn. -->
<string name="badwords_lost"> Turn lost.</string>
@ -1235,7 +1234,7 @@
\u003cbr \\\u003E
\u003cbr \\\u003E
(full link: %1$s)
(full link: %1$s )
</string>
<!-- This is the body of the text version of the invitation. A URL
@ -2089,7 +2088,7 @@
<!-- -->
<string name="new_app_availf">New version of %s</string>
<!-- -->
<string name="new_app_avail">Tap to download</string>
<string name="new_app_avail">Tap to download and install</string>
<!-- Used in formatting final scores display -->
<string name="str_resigned">Resigned</string>
<!-- Used in formatting final scores display -->
@ -2130,9 +2129,32 @@
<string name="download_path_title">Downloads Directory</string>
<string name="newgroup_label">Name your new group:</string>
<string name="list_group_delete">Delete</string>
<string name="list_group_rename">Rename</string>
<string name="list_group_default">Put new games here</string>
<string name="list_group_moveup">Move up</string>
<string name="list_group_movedown">Move down</string>
<string name="group_cur_games">My games</string>
<string name="group_new_games">New games</string>
<string name="group_confirm_del">Are you sure you want to delete
this group?</string>
<string name="group_confirm_delf">\u0020(It contains %d game[s],
which will also be deleted.)</string>
<string name="rename_group_label">Change the name of this group to:</string>
<string name="game_name_group_title">Name group</string>
<string name="cannot_delete_default_group">The group for new games
cannot be deleted."</string>
<string name="no_move_onegroup">Moving is impossible until there
is more than one group.</string>
<string name="group_namef">%1$s (%2$d games)</string>
<!-- Button shown in game over dialog triggering creation of new
game with the same players and parameters as the one that
just ended. -->
@ -2141,4 +2163,5 @@
<string name="square_tiles">Square rack tiles</string>
<string name="square_tiles_summary">Even if they can be taller</string>
<string name="change_groupf">Move game %s</string>
</resources>

View file

@ -60,5 +60,11 @@
<item name="android:layout_marginBottom">3sp</item>
</style>
<style name="spaced_buttons">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
</style>
</resources>

View file

@ -22,6 +22,7 @@ package org.eehouse.android.xw4;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
@ -33,7 +34,7 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String TABLE_NAME_DICTINFO = "dictinfo";
public static final String TABLE_NAME_GROUPS = "groups";
private static final String DB_NAME = "xwdb";
private static final int DB_VERSION = 15;
private static final int DB_VERSION = 16;
public static final String GAME_NAME = "GAME_NAME";
public static final String NUM_MOVES = "NUM_MOVES";
@ -197,6 +198,8 @@ public class DBHelper extends SQLiteOpenHelper {
case 14:
addSumColumn( db, GROUPID );
createGroupsTable( db );
case 15:
moveToCurGames( db );
// nothing yet
break;
default:
@ -260,4 +263,21 @@ public class DBHelper extends SQLiteOpenHelper {
XWPrefs.setDefaultNewGameGroup( m_context, newGroup );
}
// Move all existing games to the row previously named "cur games'
private void moveToCurGames( SQLiteDatabase db )
{
String name = m_context.getString( R.string.group_cur_games );
String[] columns = { "rowid" };
String selection = String.format( "%s = '%s'", GROUPNAME, name );
Cursor cursor = db.query( DBHelper.TABLE_NAME_GROUPS, columns,
selection, null, null, null, null );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
long rowid = cursor.getLong( cursor.getColumnIndex("rowid") );
ContentValues values = new ContentValues();
values.put( GROUPID, rowid );
db.update( DBHelper.TABLE_NAME_SUM, values, null, null );
}
cursor.close();
}
}

View file

@ -320,6 +320,7 @@ public class DBUtils {
}
db.close();
notifyListeners( rowid, false );
invalGroupsCache();
}
} // saveSummary
@ -728,6 +729,7 @@ public class DBUtils {
if ( -1 != rowid ) { // Means new game?
notifyListeners( rowid, false );
}
invalGroupsCache();
return rowid;
}
@ -782,35 +784,6 @@ public class DBUtils {
notifyListeners( lock.getRowid(), true );
}
public static long[] gamesList( Context context )
{
long[] result;
synchronized( DBUtils.class ) {
if ( null == s_cachedRowIDs ) {
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
String[] columns = { ROW_ID };
String orderBy = DBHelper.CREATE_TIME + " DESC";
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM,
columns, null, null, null,
null, orderBy );
int count = cursor.getCount();
s_cachedRowIDs = new long[count];
int index = cursor.getColumnIndex( ROW_ID );
for ( int ii = 0; cursor.moveToNext(); ++ii ) {
s_cachedRowIDs[ii] = cursor.getLong( index );
}
cursor.close();
db.close();
}
}
result = s_cachedRowIDs;
}
return result;
}
private static void clearRowIDsCache()
{
synchronized( DBUtils.class ) {
@ -865,6 +838,249 @@ public class DBUtils {
return result;
}
// Groups stuff
public static class GameGroupInfo {
public String m_name;
public boolean m_expanded;
public long m_lastMoveTime;
public boolean m_hasTurn;
public boolean m_turnLocal;
public GameGroupInfo( String name, boolean expanded ) {
m_name = name; m_expanded = expanded;
m_lastMoveTime = 0;
}
}
private static HashMap<Long,GameGroupInfo> s_groupsCache = null;
private static void invalGroupsCache()
{
s_groupsCache = null;
}
// Return map of string (group name) to info about all games in
// that group.
public static HashMap<Long,GameGroupInfo> getGroups( Context context )
{
if ( null == s_groupsCache ) {
HashMap<Long,GameGroupInfo> result =
new HashMap<Long,GameGroupInfo>();
initDB( context );
String[] columns = { ROW_ID, DBHelper.GROUPNAME,
DBHelper.EXPANDED };
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
Cursor cursor = db.query( DBHelper.TABLE_NAME_GROUPS, columns,
null, // selection
null, // args
null, // groupBy
null, // having
null //orderby
);
int idIndex = cursor.getColumnIndex( ROW_ID );
int nameIndex = cursor.getColumnIndex( DBHelper.GROUPNAME );
int expandedIndex = cursor.getColumnIndex( DBHelper.EXPANDED );
while ( cursor.moveToNext() ) {
String name = cursor.getString( nameIndex );
long id = cursor.getLong( idIndex );
Assert.assertNotNull( name );
boolean expanded = 0 != cursor.getInt( expandedIndex );
result.put( id, new GameGroupInfo( name, expanded ) );
}
cursor.close();
Iterator<Long> iter = result.keySet().iterator();
while ( iter.hasNext() ) {
Long id = iter.next();
GameGroupInfo ggi = result.get( id );
readTurnInfo( db, id, ggi );
}
db.close();
}
s_groupsCache = result;
}
return s_groupsCache;
} // getGroups
private static void readTurnInfo( SQLiteDatabase db, long id,
GameGroupInfo ggi )
{
String[] columns = { DBHelper.LASTMOVE, DBHelper.GIFLAGS,
DBHelper.TURN };
String orderBy = DBHelper.LASTMOVE;
String selection = String.format( "%s=%d", DBHelper.GROUPID, id );
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection,
null, // args
null, // groupBy,
null, // having
orderBy
);
// We want the earliest LASTPLAY_TIME (i.e. the first we see
// since they're in order) that's a local turn, if any,
// otherwise a non-local turn.
long lastPlayTimeLocal = 0;
long lastPlayTimeRemote = 0;
int indexLPT = cursor.getColumnIndex( DBHelper.LASTMOVE );
int indexFlags = cursor.getColumnIndex( DBHelper.GIFLAGS );
int turnFlags = cursor.getColumnIndex( DBHelper.TURN );
while ( cursor.moveToNext() && 0 == lastPlayTimeLocal ) {
int flags = cursor.getInt( indexFlags );
int turn = cursor.getInt( turnFlags );
Boolean isLocal = GameSummary.localTurnNext( flags, turn );
if ( null != isLocal ) {
long lpt = cursor.getLong( indexLPT );
if ( isLocal ) {
lastPlayTimeLocal = lpt;
} else if ( 0 == lastPlayTimeRemote ) {
lastPlayTimeRemote = lpt;
}
}
}
cursor.close();
ggi.m_hasTurn = 0 != lastPlayTimeLocal || 0 != lastPlayTimeRemote;
if ( ggi.m_hasTurn ) {
ggi.m_turnLocal = 0 != lastPlayTimeLocal;
if ( ggi.m_turnLocal ) {
ggi.m_lastMoveTime = lastPlayTimeLocal;
} else {
ggi.m_lastMoveTime = lastPlayTimeRemote;
}
// DateFormat df = DateFormat.getDateTimeInstance( DateFormat.SHORT,
// DateFormat.SHORT );
// DbgUtils.logf( "using last play time %s for",
// df.format( new Date( 1000 * ggi.m_lastMoveTime ) ) );
}
}
public static long[] getGroupGames( Context context, long groupID )
{
long[] result = null;
initDB( context );
String[] columns = { ROW_ID };
String selection = String.format( "%s=%d", DBHelper.GROUPID, groupID );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
String orderBy = DBHelper.CREATE_TIME + " DESC";
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, // selection
null, // args
null, // groupBy
null, // having
orderBy
);
int index = cursor.getColumnIndex( ROW_ID );
result = new long[ cursor.getCount() ];
for ( int ii = 0; cursor.moveToNext(); ++ii ) {
long rowid = cursor.getInt( index );
result[ii] = rowid;
}
cursor.close();
db.close();
}
return result;
}
public static long getGroupForGame( Context context, long rowid )
{
long result = ROWID_NOTFOUND;
initDB( context );
String[] columns = { DBHelper.GROUPID };
String selection = String.format( ROW_ID_FMT, rowid );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, // selection
null, // args
null, // groupBy
null, // having
null //orderby
);
if ( cursor.moveToNext() ) {
int index = cursor.getColumnIndex( DBHelper.GROUPID );
result = cursor.getLong( index );
}
cursor.close();
db.close();
}
return result;
}
public static long addGroup( Context context, String name )
{
long rowid = ROWID_NOTFOUND;
if ( null != name && 0 < name.length() ) {
HashMap<Long,GameGroupInfo> gameInfo = getGroups( context );
if ( null == gameInfo.get( name ) ) {
ContentValues values = new ContentValues();
values.put( DBHelper.GROUPNAME, name );
values.put( DBHelper.EXPANDED, 0 );
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
rowid = db.insert( DBHelper.TABLE_NAME_GROUPS, null,
values );
db.close();
}
invalGroupsCache();
}
}
return rowid;
}
public static void deleteGroup( Context context, long groupid )
{
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
// Nuke games having this group id
String selection =
String.format( "%s=%d", DBHelper.GROUPID, groupid );
db.delete( DBHelper.TABLE_NAME_SUM, selection, null );
// And nuke the group record itself
selection = String.format( ROW_ID_FMT, groupid );
db.delete( DBHelper.TABLE_NAME_GROUPS, selection, null );
db.close();
}
invalGroupsCache();
}
public static void setGroupName( Context context, long groupid,
String name )
{
ContentValues values = new ContentValues();
values.put( DBHelper.GROUPNAME, name );
updateRow( context, DBHelper.TABLE_NAME_GROUPS, groupid, values );
invalGroupsCache();
}
public static void setGroupExpanded( Context context, long groupid,
boolean expanded )
{
ContentValues values = new ContentValues();
values.put( DBHelper.EXPANDED, expanded? 1 : 0 );
updateRow( context, DBHelper.TABLE_NAME_GROUPS, groupid, values );
invalGroupsCache();
}
// Change group id of a game
public static void moveGame( Context context, long gameid, long groupid )
{
ContentValues values = new ContentValues();
values.put( DBHelper.GROUPID, groupid );
updateRow( context, DBHelper.TABLE_NAME_SUM, gameid, values );
}
private static String getChatHistoryStr( Context context, long rowid )
{
String result = null;

View file

@ -23,7 +23,6 @@ package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ExpandableListActivity;
import android.content.Context;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface;
@ -33,7 +32,6 @@ import android.content.res.Resources;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.ContextMenu;
@ -47,9 +45,7 @@ import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ExpandableListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import junit.framework.Assert;
@ -58,7 +54,7 @@ import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.jni.JNIUtilsImpl;
import org.eehouse.android.xw4.DictUtils.DictLoc;
public class DictsActivity extends ExpandableListActivity
public class DictsActivity extends XWExpandableListActivity
implements View.OnClickListener, XWListItem.DeleteCallback,
MountEventReceiver.SDCardNotifiee, DlgDelegate.DlgClickNotify,
DictImportActivity.DownloadFinishedListener {
@ -86,10 +82,8 @@ public class DictsActivity extends ExpandableListActivity
private String m_deleteDict = null;
private String m_download;
private ExpandableListView m_expView;
private DlgDelegate m_delegate;
private String[] m_locNames;
private DictListAdapter m_adapter;
private Handler m_handler;
private long m_packedPosition;
private DictLoc m_moveFromLoc;
@ -338,7 +332,6 @@ public class DictsActivity extends ExpandableListActivity
int lang = intent.getIntExtra( MultiService.LANG, -1 );
String name = intent.getStringExtra( MultiService.DICT );
m_launchedForMissing = true;
m_handler = new Handler();
DictImportActivity
.downloadDictInBack( DictsActivity.this, lang,
name, DictsActivity.this );
@ -355,7 +348,7 @@ public class DictsActivity extends ExpandableListActivity
break;
default:
dialog = m_delegate.onCreateDialog( id );
dialog = super.onCreateDialog( id );
doRemove = false;
break;
}
@ -371,7 +364,6 @@ public class DictsActivity extends ExpandableListActivity
protected void onPrepareDialog( int id, Dialog dialog )
{
super.onPrepareDialog( id, dialog );
m_delegate.onPrepareDialog( id, dialog );
if ( MOVE_DICT == id ) {
// The move button should always start out disabled
@ -399,7 +391,6 @@ public class DictsActivity extends ExpandableListActivity
Resources res = getResources();
m_locNames = res.getStringArray( R.array.loc_names );
m_delegate = new DlgDelegate( this, this, savedInstanceState );
m_factory = LayoutInflater.from( this );
m_download = getString( R.string.download_dicts );
@ -447,7 +438,6 @@ public class DictsActivity extends ExpandableListActivity
protected void onSaveInstanceState( Bundle outState )
{
super.onSaveInstanceState( outState );
m_delegate.onSaveInstanceState( outState );
outState.putLong( PACKED_POSITION, m_packedPosition );
outState.putString( NAME, m_name );
@ -622,8 +612,7 @@ public class DictsActivity extends ExpandableListActivity
}
}
m_delegate.showConfirmThen( msg, R.string.button_delete,
DELETE_DICT_ACTION );
showConfirmThen( msg, R.string.button_delete, DELETE_DICT_ACTION );
}
// MountEventReceiver.SDCardNotifiee interface
@ -632,7 +621,7 @@ public class DictsActivity extends ExpandableListActivity
DbgUtils.logf( "DictsActivity.cardMounted(%b)", nowMounted );
// post so other SDCardNotifiee implementations get a chance
// to process first: avoid race conditions
new Handler().post( new Runnable() {
post( new Runnable() {
public void run() {
mkListAdapter();
expandGroups();
@ -773,7 +762,7 @@ public class DictsActivity extends ExpandableListActivity
public void downloadFinished( String name, final boolean success )
{
if ( m_launchedForMissing ) {
m_handler.post( new Runnable() {
post( new Runnable() {
public void run() {
if ( success ) {
Intent intent = getIntent();

View file

@ -79,6 +79,7 @@ public class ExpiringDelegate {
if ( haveTurnLocal ) {
setBackground();
} else {
m_view.setBackgroundDrawable( null );
m_view.setWillNotDraw( false );
}
}

View file

@ -41,7 +41,17 @@ class ExpiringTextView extends TextView {
if ( null == m_delegate ) {
m_delegate = new ExpiringDelegate( m_context, this, handler );
}
m_delegate.configure( haveTurn, haveTurnLocal, startSecs );
setPct( haveTurn, haveTurnLocal, startSecs );
}
public void setPct( boolean haveTurn, boolean haveTurnLocal,
long startSecs )
{
if ( null != m_delegate ) {
m_delegate.configure( haveTurn, haveTurnLocal, startSecs );
} else {
DbgUtils.logf( "m_delegate null; skipping" );
}
}
@Override

View file

@ -20,93 +20,321 @@
package org.eehouse.android.xw4;
import android.content.Context;
import android.database.DataSetObserver;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import java.util.HashMap; // class is not synchronized
import java.util.HashSet;
import java.util.Set;
import java.util.Iterator;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.*;
import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.DBUtils.GameGroupInfo;
public class GameListAdapter extends XWListAdapter {
public class GameListAdapter implements ExpandableListAdapter {
private Context m_context;
private ListView m_list;
private LayoutInflater m_factory;
private ExpandableListView m_list;
private int m_fieldID;
private Handler m_handler;
private LoadItemCB m_cb;
private long[] m_positions;
public interface LoadItemCB {
public void itemClicked( long rowid, GameSummary summary );
}
public GameListAdapter( Context context, ListView list,
Handler handler, LoadItemCB cb, String fieldName ) {
super( DBUtils.gamesList(context).length );
public GameListAdapter( Context context, ExpandableListView list,
Handler handler, LoadItemCB cb, long[] positions,
String fieldName )
{
m_context = context;
m_list = list;
m_handler = handler;
m_cb = cb;
m_factory = LayoutInflater.from( context );
m_positions = checkPositions( positions );
m_fieldID = fieldToID( fieldName );
}
@Override
public int getCount() {
return DBUtils.gamesList(m_context).length;
public long[] getPositions()
{
Set<Long> keys = gameInfo().keySet(); // do not modify!!!!
if ( null == m_positions || m_positions.length != keys.size() ) {
HashSet<Long> unused = new HashSet<Long>( keys );
long[] newArray = new long[unused.size()];
// First copy the existing values, in order
int nextIndx = 0;
if ( null != m_positions ) {
for ( long id: m_positions ) {
if ( unused.contains( id ) ) {
newArray[nextIndx++] = id;
unused.remove( id );
}
}
}
// Then copy in what's left
Iterator<Long> iter = unused.iterator();
while ( iter.hasNext() ) {
newArray[nextIndx++] = iter.next();
}
m_positions = newArray;
}
return m_positions;
}
// Views. A view depends on a summary, which takes time to load.
// When one needs loading it's done via an async task.
public View getView( int position, View convertView, ViewGroup parent )
public boolean moveGroup( long groupid, int moveBy )
{
GameListItem result = (GameListItem)
m_factory.inflate( R.layout.game_list_item, null );
result.init( m_handler, DBUtils.gamesList(m_context)[position],
m_fieldID, m_cb );
int src = getGroupPosition( groupid );
int dest = src + moveBy;
long[] positions = getPositions();
boolean success = 0 <= dest && dest < positions.length;
if ( success ) {
long tmp = positions[src];
positions[src] = positions[dest];
positions[dest] = tmp;
}
return success;
}
public void expandGroups( ExpandableListView view )
{
HashMap<Long,GameGroupInfo> info = gameInfo();
for ( int ii = 0; ii < info.size(); ++ii ) {
GameGroupInfo ggi = getInfoForGroup( ii );
if ( ggi.m_expanded ) {
view.expandGroup( ii );
}
}
}
public long getRowIDFor( int group, int child )
{
long rowid = DBUtils.ROWID_NOTFOUND;
long[] rows = getRows( getPositions()[group] );
if ( child < rows.length ) {
rowid = rows[child];
}
return rowid;
}
public long getRowIDFor( long packedPosition )
{
int childPosition = ExpandableListView.
getPackedPositionChild( packedPosition );
int groupPosition = ExpandableListView.
getPackedPositionGroup( packedPosition );
return getRowIDFor( groupPosition, childPosition );
}
public long getGroupIDFor( int groupPos )
{
long id = getPositions()[groupPos];
return id;
}
public String groupName( long groupid )
{
HashMap<Long,GameGroupInfo> info = gameInfo();
GameGroupInfo ggi = info.get( groupid );
return ggi.m_name;
}
//////////////////////////////////////////////////////////////////////////
// ExpandableListAdapter interface
//////////////////////////////////////////////////////////////////////////
public long getCombinedGroupId( long groupId )
{
return groupId;
}
public long getCombinedChildId( long groupId, long childId )
{
return groupId << 16 | childId;
}
public boolean isEmpty() { return false; }
public void onGroupCollapsed( int groupPosition )
{
long groupid = getGroupIDFor( groupPosition );
DBUtils.setGroupExpanded( m_context, groupid, false );
}
public void onGroupExpanded( int groupPosition )
{
long groupid = getGroupIDFor( groupPosition );
DBUtils.setGroupExpanded( m_context, groupid, true );
}
public boolean areAllItemsEnabled() { return true; }
public boolean isChildSelectable( int groupPosition, int childPosition )
{ return true; }
public View getChildView( int groupPosition, int childPosition,
boolean isLastChild, View convertView,
ViewGroup parent)
{
View result = null;
if ( null != convertView ) {
// DbgUtils.logf( "getChildView gave non-null convertView" );
if ( convertView instanceof GameListItem ) {
GameListItem child = (GameListItem)convertView;
long rowid = getRowIDFor( groupPosition, childPosition );
if ( child.getRowID() == rowid ) {
DbgUtils.logf( "reusing child for rowid %d", rowid );
result = child;
}
}
}
if ( null == result ) {
result = getChildView( groupPosition, childPosition );
}
return result;
}
private View getChildView( int groupPosition, int childPosition )
{
long rowid = getRowIDFor( groupPosition, childPosition );
GameListItem result =
GameListItem.makeForRow( m_context, rowid, m_handler,
groupPosition, m_fieldID, m_cb );
return result;
}
public View getGroupView( int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent )
{
// if ( null != convertView ) {
// DbgUtils.logf( "getGroupView gave non-null convertView" );
// }
GameListGroup view = (GameListGroup)
Utils.inflate(m_context, R.layout.game_list_group );
view.setGroupPosition( groupPosition );
if ( !isExpanded ) {
GameGroupInfo ggi = getInfoForGroup( groupPosition );
view.setPct( m_handler, ggi.m_hasTurn, ggi.m_turnLocal,
ggi.m_lastMoveTime );
}
int nKids = getChildrenCount( groupPosition );
String name = m_context.getString( R.string.group_namef,
groupNames()[groupPosition], nKids );
view.setText( name );
return view;
}
public boolean hasStableIds() { return false; }
public long getChildId( int groupPosition, int childPosition )
{
return childPosition;
}
public long getGroupId( int groupPosition )
{
return groupPosition;
}
public Object getChild( int groupPosition, int childPosition )
{
return null;
}
public Object getGroup( int groupPosition )
{
return null;
}
public int getChildrenCount( int groupPosition )
{
long[] rows = getRows( getPositions()[groupPosition] );
return rows.length;
}
public int getGroupCount()
{
return gameInfo().size();
}
public void registerDataSetObserver( DataSetObserver obs ){}
public void unregisterDataSetObserver( DataSetObserver obs ){}
public void inval( long rowid )
{
GameListItem child = getItemFor( rowid );
GameListItem child = getGameItemFor( rowid );
int groupPosition;
if ( null != child && child.getRowID() == rowid ) {
child.forceReload();
groupPosition = child.getGroupPosition();
} else {
DbgUtils.logf( "no child for rowid %d", rowid );
// DbgUtils.logf( "no child for rowid %d", rowid );
GameListItem.inval( rowid );
m_list.invalidate();
long groupID = DBUtils.getGroupForGame( m_context, rowid );
groupPosition = getGroupPosition( groupID );
}
reloadGroup( groupPosition );
}
public void invalName( long rowid )
{
GameListItem item = getItemFor( rowid );
GameListItem item = getGameItemFor( rowid );
if ( null != item ) {
item.invalName();
}
}
private long[] getRows( long groupID )
{
return DBUtils.getGroupGames( m_context, groupID );
}
public String[] groupNames()
{
HashMap<Long,GameGroupInfo> info = gameInfo();
long[] positions = getPositions();
String[] names = new String[ positions.length ];
for ( int ii = 0; ii < names.length; ++ii ) {
names[ii] = info.get(positions[ii]).m_name;
}
return names;
}
public int getGroupPosition( long groupid )
{
int result = -1;
long[] positions = getPositions();
for ( int pos = 0; pos < positions.length; ++pos ) {
if ( positions[pos] == groupid ) {
result = pos;
break;
}
}
return result;
}
public boolean setField( String fieldName )
{
boolean changed = false;
int newID = fieldToID( fieldName );
if ( -1 == newID ) {
if ( XWApp.DEBUG ) {
DbgUtils.logf( "GameListAdapter.setField(): unable to match"
+ " fieldName %s", fieldName );
}
DbgUtils.logf( "GameListAdapter.setField(): unable to match"
+ " fieldName %s", fieldName );
} else if ( m_fieldID != newID ) {
if ( XWApp.DEBUG ) {
DbgUtils.logf( "setField: clearing views cache for change"
+ " from %d to %d", m_fieldID, newID );
}
m_fieldID = newID;
// return true so caller will do onContentChanged.
// There's no other way to signal GameListItem instances
@ -116,12 +344,41 @@ public class GameListAdapter extends XWListAdapter {
return changed;
}
private GameListItem getItemFor( long rowid )
private GameGroupInfo getInfoForGroup( int groupPosition )
{
return gameInfo().get( getPositions()[groupPosition] );
}
private GameListItem getGameItemFor( long rowid )
{
GameListItem result = null;
int position = positionFor( rowid );
if ( 0 <= position ) {
result = (GameListItem)m_list.getChildAt( position );
int count = m_list.getChildCount();
for ( int ii = 0; ii < count; ++ii ) {
View view = m_list.getChildAt( ii );
if ( view instanceof GameListItem ) {
GameListItem tryme = (GameListItem)view;
if ( tryme.getRowID() == rowid ) {
result = tryme;
break;
}
}
}
return result;
}
private GameListGroup getGroupItemFor( int groupPosition )
{
GameListGroup result = null;
int count = m_list.getChildCount();
for ( int ii = 0; ii < count; ++ii ) {
View view = m_list.getChildAt( ii );
if ( view instanceof GameListGroup ) {
GameListGroup tryme = (GameListGroup)view;
if ( tryme.getGroupPosition() == groupPosition ) {
result = tryme;
break;
}
}
}
return result;
}
@ -144,16 +401,32 @@ public class GameListAdapter extends XWListAdapter {
return result;
}
private int positionFor( long rowid )
private void reloadGroup( int groupPosition )
{
int position = -1;
long[] rowids = DBUtils.gamesList( m_context );
for ( int ii = 0; ii < rowids.length; ++ii ) {
if ( rowids[ii] == rowid ) {
position = ii;
break;
GameListGroup group = getGroupItemFor( groupPosition );
if ( null != group ) {
GameGroupInfo ggi = getInfoForGroup( groupPosition );
group.setPct( ggi.m_hasTurn, ggi.m_turnLocal, ggi.m_lastMoveTime );
}
}
private HashMap<Long,GameGroupInfo> gameInfo()
{
return DBUtils.getGroups( m_context );
}
private long[] checkPositions( long[] positions )
{
long[] result = positions;
if ( null != positions ) {
Set<Long> posns = gameInfo().keySet();
for ( long id : positions ) {
if ( ! posns.contains( id ) ) {
result = null;
break;
}
}
}
return position;
return result;
}
}
}

View file

@ -0,0 +1,46 @@
/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
/*
* Copyright 2012 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.
*/
package org.eehouse.android.xw4;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import org.eehouse.android.xw4.DBUtils.GameGroupInfo;
public class GameListGroup extends ExpiringTextView {
private int m_groupPosition;
public GameListGroup( Context cx, AttributeSet as )
{
super( cx, as );
}
public void setGroupPosition( int groupPosition )
{
m_groupPosition = groupPosition;
}
public int getGroupPosition()
{
return m_groupPosition;
}
}

View file

@ -57,6 +57,7 @@ public class GameListItem extends LinearLayout
private GameListAdapter.LoadItemCB m_cb;
private int m_fieldID;
private int m_loadingCount;
private int m_groupPosition;
public GameListItem( Context cx, AttributeSet as )
{
@ -68,11 +69,12 @@ public class GameListItem extends LinearLayout
m_loadingCount = 0;
}
public void init( Handler handler, long rowid, int fieldID,
GameListAdapter.LoadItemCB cb )
private void init( Handler handler, long rowid, int groupPosition,
int fieldID, GameListAdapter.LoadItemCB cb )
{
m_handler = handler;
m_rowid = rowid;
m_groupPosition = groupPosition;
m_fieldID = fieldID;
m_cb = cb;
@ -128,6 +130,11 @@ public class GameListItem extends LinearLayout
return m_rowid;
}
public int getGroupPosition()
{
return m_groupPosition;
}
// View.OnClickListener interface
public void onClick( View view ) {
m_expanded = !m_expanded;
@ -297,6 +304,17 @@ public class GameListItem extends LinearLayout
}
} // class LoadItemTask
public static GameListItem makeForRow( Context context, long rowid,
Handler handler, int groupPosition,
int fieldID,
GameListAdapter.LoadItemCB cb )
{
GameListItem result =
(GameListItem)Utils.inflate( context, R.layout.game_list_item );
result.init( handler, rowid, groupPosition, fieldID, cb );
return result;
}
public static void inval( long rowid )
{
synchronized( s_invalRows ) {

View file

@ -190,10 +190,10 @@ public class GameUtils {
return rowid;
}
public static void deleteGame( Context context, long rowid,
boolean informNow )
public static boolean deleteGame( Context context, long rowid,
boolean informNow )
{
DbgUtils.logf( "deleteGame(rowid=%d)", rowid );
boolean success;
// does this need to be synchronized?
GameLock lock = new GameLock( rowid, true );
if ( lock.tryLock() ) {
@ -201,8 +201,25 @@ public class GameUtils {
Utils.cancelNotification( context, (int)rowid );
DBUtils.deleteGame( context, lock );
lock.unlock();
success = true;
} else {
DbgUtils.logf( "deleteGame: unable to delete rowid %d", rowid );
success = false;
}
return success;
}
public static void deleteGroup( Context context, long groupid )
{
int nSuccesses = 0;
long[] rowids = DBUtils.getGroupGames( context, groupid );
for ( int ii = rowids.length - 1; ii >= 0; --ii ) {
if ( deleteGame( context, rowids[ii], ii == 0 ) ) {
++nSuccesses;
}
}
if ( rowids.length == nSuccesses ) {
DBUtils.deleteGroup( context, groupid );
}
}
@ -499,13 +516,6 @@ public class GameUtils {
return allHere;
}
public static boolean gameDictsHere( Context context, int indx,
String[][] name, int[] lang )
{
long rowid = DBUtils.gamesList( context )[indx];
return gameDictsHere( context, rowid, name, lang );
}
public static String newName( Context context )
{
return "untitled";

View file

@ -41,29 +41,36 @@ import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ExpandableListView;
import android.widget.LinearLayout;
import android.widget.ListView;
import java.io.File;
import java.util.Date;
// import android.telephony.PhoneStateListener;
// import android.telephony.TelephonyManager;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.*;
public class GamesList extends XWListActivity
public class GamesList extends XWExpandableListActivity
implements DBUtils.DBChangeListener,
GameListAdapter.LoadItemCB,
DictImportActivity.DownloadFinishedListener {
private static final int WARN_NODICT = DlgDelegate.DIALOG_LAST + 1;
private static final int WARN_NODICT_SUBST = WARN_NODICT + 1;
private static final int WARN_NODICT_NEW = WARN_NODICT + 2;
private static final int SHOW_SUBST = WARN_NODICT + 3;
private static final int GET_NAME = WARN_NODICT + 4;
private static final int RENAME_GAME = WARN_NODICT + 5;
private static final int SHOW_SUBST = WARN_NODICT + 2;
private static final int GET_NAME = WARN_NODICT + 3;
private static final int RENAME_GAME = WARN_NODICT + 4;
private static final int NEW_GROUP = WARN_NODICT + 5;
private static final int RENAME_GROUP = WARN_NODICT + 6;
private static final int CHANGE_GROUP = WARN_NODICT + 7;
private static final int WARN_NODICT_NEW = WARN_NODICT + 8;
private static final String SAVE_ROWID = "SAVE_ROWID";
private static final String SAVE_GROUPID = "SAVE_GROUPID";
private static final String SAVE_DICTNAMES = "SAVE_DICTNAMES";
private static final String RELAYIDS_EXTRA = "relayids";
@ -73,9 +80,9 @@ public class GamesList extends XWListActivity
private static final int NEW_NET_GAME_ACTION = 1;
private static final int RESET_GAME_ACTION = 2;
private static final int DELETE_GAME_ACTION = 3;
private static final int DELETE_ALL_ACTION = 4;
private static final int SYNC_MENU_ACTION = 5;
private static final int NEW_FROM_ACTION = 6;
private static final int SYNC_MENU_ACTION = 4;
private static final int NEW_FROM_ACTION = 5;
private static final int DELETE_GROUP_ACTION = 6;
private static final int[] DEBUGITEMS = { R.id.gamel_menu_loaddb
, R.id.gamel_menu_storedb
, R.id.gamel_menu_checkupdates
@ -90,12 +97,16 @@ public class GamesList extends XWListActivity
private String[] m_sameLangDicts;
private int m_missingDictLang;
private long m_rowid;
private long m_groupid;
private String m_nameField;
private NetLaunchInfo m_netLaunchInfo;
private GameNamer m_namer;
@Override
protected Dialog onCreateDialog( int id )
{
DialogInterface.OnClickListener lstnr;
DialogInterface.OnClickListener lstnr2;
LinearLayout layout;
Dialog dialog = super.onCreateDialog( id );
@ -178,36 +189,87 @@ public class GamesList extends XWListActivity
.setNegativeButton( R.string.button_cancel, null )
.setSingleChoiceItems( m_sameLangDicts, 0, null )
.create();
;
// Force destruction so onCreateDialog() will get
// called next time and we can insert a different
// list. There seems to be no way to change the list
// inside onPrepareDialog().
dialog.setOnDismissListener(new DialogInterface.
OnDismissListener() {
public void onDismiss(DialogInterface dlg) {
removeDialog( SHOW_SUBST );
}
});
Utils.setRemoveOnDismiss( this, dialog, id );
break;
case RENAME_GAME:
final GameNamer namerView =
(GameNamer)Utils.inflate( this, R.layout.rename_game );
namerView.setName( GameUtils.getName( this, m_rowid ) );
namerView.setLabel( R.string.rename_label );
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
String name = namerView.getName();
String name = m_namer.getName();
DBUtils.setName( GamesList.this, m_rowid, name );
m_adapter.invalName( m_rowid );
}
};
dialog = buildNamerDlg( GameUtils.getName( this, m_rowid ),
R.string.rename_label,
R.string.game_rename_title,
lstnr, RENAME_GAME );
break;
case RENAME_GROUP:
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
String name = m_namer.getName();
DBUtils.setGroupName( GamesList.this, m_groupid,
name );
m_adapter.inval( m_rowid );
onContentChanged();
}
};
dialog = buildNamerDlg( m_adapter.groupName( m_groupid ),
R.string.rename_group_label,
R.string.game_name_group_title,
lstnr, RENAME_GROUP );
break;
case NEW_GROUP:
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
String name = m_namer.getName();
DBUtils.addGroup( GamesList.this, name );
// m_adapter.inval();
onContentChanged();
}
};
dialog = buildNamerDlg( "", R.string.newgroup_label,
R.string.game_name_group_title,
lstnr, RENAME_GROUP );
Utils.setRemoveOnDismiss( this, dialog, id );
break;
case CHANGE_GROUP:
final long startGroup = DBUtils.getGroupForGame( this, m_rowid );
final int[] selItem = {-1}; // hack!!!!
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlgi, int item ) {
selItem[0] = item;
AlertDialog dlg = (AlertDialog)dlgi;
Button btn =
dlg.getButton( AlertDialog.BUTTON_POSITIVE );
long newGroup = m_adapter.getGroupIDFor( item );
btn.setEnabled( newGroup != startGroup );
}
};
lstnr2 = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
Assert.assertTrue( -1 != selItem[0] );
long gid = m_adapter.getGroupIDFor( selItem[0] );
DBUtils.moveGame( GamesList.this, m_rowid, gid );
onContentChanged();
}
};
String[] groups = m_adapter.groupNames();
int curGroupPos = m_adapter.getGroupPosition( startGroup );
String name = GameUtils.getName( this, m_rowid );
dialog = new AlertDialog.Builder( this )
.setTitle( R.string.game_rename_title )
.setTitle( getString( R.string.change_groupf, name ) )
.setSingleChoiceItems( groups, curGroupPos, lstnr )
.setPositiveButton( R.string.button_move, lstnr2 )
.setNegativeButton( R.string.button_cancel, null )
.setPositiveButton( R.string.button_ok, lstnr )
.setView( namerView )
.create();
Utils.setRemoveOnDismiss( this, dialog, id );
break;
@ -248,6 +310,16 @@ public class GamesList extends XWListActivity
return dialog;
} // onCreateDialog
@Override protected void onPrepareDialog( int id, Dialog dialog )
{
super.onPrepareDialog( id, dialog );
if ( CHANGE_GROUP == id ) {
((AlertDialog)dialog).getButton( AlertDialog.BUTTON_POSITIVE )
.setEnabled( false );
}
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
@ -258,7 +330,7 @@ public class GamesList extends XWListActivity
getBundledData( savedInstanceState );
setContentView(R.layout.game_list);
registerForContextMenu( getListView() );
registerForContextMenu( getExpandableListView() );
DBUtils.setDBChangeListener( this );
boolean isUpgrade = Utils.firstBootThisVersion( this );
@ -280,11 +352,21 @@ public class GamesList extends XWListActivity
// R.string.key_notagain_newgame );
}
});
newGameB = (Button)findViewById(R.id.new_group);
newGameB.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick( View v ) {
showDialog( NEW_GROUP );
}
});
String field = CommonPrefs.getSummaryField( this );
m_adapter = new GameListAdapter( this, getListView(), new Handler(),
this, field );
long[] positions = XWPrefs.getGroupPositions( this );
m_adapter = new GameListAdapter( this, getExpandableListView(),
new Handler(), this, positions,
field );
setListAdapter( m_adapter );
m_adapter.expandGroups( getExpandableListView() );
NetUtils.informOfDeaths( this );
@ -318,13 +400,13 @@ public class GamesList extends XWListActivity
boolean hide = CommonPrefs.getHideIntro( this );
int hereOrGone = hide ? View.GONE : View.VISIBLE;
for ( int id : new int[]{ R.id.empty_games_list,
R.id.new_game } ) {
R.id.new_buttons } ) {
View view = findViewById( id );
view.setVisibility( hereOrGone );
}
View empty = findViewById( R.id.empty_list_msg );
empty.setVisibility( hide ? View.VISIBLE : View.GONE );
getListView().setEmptyView( hide? empty : null );
getExpandableListView().setEmptyView( hide? empty : null );
// TelephonyManager mgr =
// (TelephonyManager)getSystemService( Context.TELEPHONY_SERVICE );
@ -340,7 +422,8 @@ public class GamesList extends XWListActivity
// (TelephonyManager)getSystemService( Context.TELEPHONY_SERVICE );
// mgr.listen( m_phoneStateListener, PhoneStateListener.LISTEN_NONE );
// m_phoneStateListener = null;
long[] positions = m_adapter.getPositions();
XWPrefs.setGroupPositions( this, positions );
super.onStop();
}
@ -356,6 +439,7 @@ public class GamesList extends XWListActivity
{
super.onSaveInstanceState( outState );
outState.putLong( SAVE_ROWID, m_rowid );
outState.putLong( SAVE_GROUPID, m_groupid );
outState.putString( SAVE_DICTNAMES, m_missingDictName );
if ( null != m_netLaunchInfo ) {
m_netLaunchInfo.putSelf( outState );
@ -366,6 +450,7 @@ public class GamesList extends XWListActivity
{
if ( null != bundle ) {
m_rowid = bundle.getLong( SAVE_ROWID );
m_groupid = bundle.getLong( SAVE_GROUPID );
m_netLaunchInfo = new NetLaunchInfo( bundle );
m_missingDictName = bundle.getString( SAVE_DICTNAMES );
}
@ -457,12 +542,6 @@ public class GamesList extends XWListActivity
case DELETE_GAME_ACTION:
GameUtils.deleteGame( this, m_rowid, true );
break;
case DELETE_ALL_ACTION:
long[] games = DBUtils.gamesList( this );
for ( int ii = games.length - 1; ii >= 0; --ii ) {
GameUtils.deleteGame( this, games[ii], ii == 0 );
}
break;
case SYNC_MENU_ACTION:
doSyncMenuitem();
break;
@ -472,40 +551,88 @@ public class GamesList extends XWListActivity
m_adapter.inval( newid );
}
break;
case DELETE_GROUP_ACTION:
GameUtils.deleteGroup( this, m_groupid );
onContentChanged();
break;
default:
Assert.fail();
}
}
}
@Override
public void onContentChanged()
{
super.onContentChanged();
if ( null != m_adapter ) {
m_adapter.expandGroups( getExpandableListView() );
}
}
@Override
public void onCreateContextMenu( ContextMenu menu, View view,
ContextMenuInfo menuInfo )
{
MenuInflater inflater = getMenuInflater();
inflater.inflate( R.menu.games_list_item_menu, menu );
ExpandableListView.ExpandableListContextMenuInfo info
= (ExpandableListView.ExpandableListContextMenuInfo)menuInfo;
long packedPos = info.packedPosition;
int childPos = ExpandableListView.getPackedPositionChild( packedPos );
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo)menuInfo;
int position = info.position;
long rowid = DBUtils.gamesList( this )[position];
String title = GameUtils.getName( this, rowid );
String name;
if ( 0 <= childPos ) { // game case
MenuInflater inflater = getMenuInflater();
inflater.inflate( R.menu.games_list_item_menu, menu );
long rowid = m_adapter.getRowIDFor( packedPos );
name = GameUtils.getName( this, rowid );
} else { // group case
MenuInflater inflater = getMenuInflater();
inflater.inflate( R.menu.games_list_group_menu, menu );
int pos = ExpandableListView.getPackedPositionGroup( packedPos );
name = m_adapter.groupNames()[pos];
if ( 0 == pos ) {
Utils.setItemEnabled( menu, R.id.list_group_moveup, false );
}
if ( pos + 1 == m_adapter.getGroupCount() ) {
Utils.setItemEnabled( menu, R.id.list_group_movedown, false );
}
if ( XWPrefs.getDefaultNewGameGroup( this )
== m_adapter.getGroupIDFor( pos ) ) {
Utils.setItemEnabled( menu, R.id.list_group_default, false );
Utils.setItemEnabled( menu, R.id.list_group_delete, false );
}
}
menu.setHeaderTitle( getString( R.string.game_item_menu_titlef,
title ) );
name ) );
}
@Override
public boolean onContextItemSelected( MenuItem item )
{
AdapterView.AdapterContextMenuInfo info;
ExpandableListContextMenuInfo info;
try {
info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
info = (ExpandableListContextMenuInfo)item.getMenuInfo();
} catch (ClassCastException cce) {
DbgUtils.loge( cce );
return false;
}
return handleMenuItem( item.getItemId(), info.position );
long packedPos = info.packedPosition;
int childPos = ExpandableListView.getPackedPositionChild( packedPos );
int groupPos = ExpandableListView.getPackedPositionGroup(packedPos);
int menuID = item.getItemId();
boolean handled;
if ( 0 <= childPos ) {
long rowid = m_adapter.getRowIDFor( groupPos, childPos );
handled = handleGameMenuItem( menuID, rowid );
} else {
handled = handleGroupMenuItem( menuID, groupPos );
}
return handled;
} // onContextItemSelected
@Override
@ -520,7 +647,7 @@ public class GamesList extends XWListActivity
@Override
public boolean onPrepareOptionsMenu( Menu menu )
{
boolean visible = XWPrefs.getDebugEnabled( this ) ;
boolean visible = XWPrefs.getDebugEnabled( this );
for ( int id : DEBUGITEMS ) {
MenuItem item = menu.findItem( id );
item.setVisible( visible );
@ -544,12 +671,8 @@ public class GamesList extends XWListActivity
startNewGameActivity();
break;
case R.id.gamel_menu_delete_all:
if ( DBUtils.gamesList( this ).length > 0 ) {
showConfirmThen( R.string.confirm_delete_all,
R.string.button_delete, DELETE_ALL_ACTION );
}
handled = true;
case R.id.gamel_menu_newgroup:
showDialog( NEW_GROUP );
break;
case R.id.gamel_menu_dicts:
@ -615,12 +738,12 @@ public class GamesList extends XWListActivity
} );
}
private boolean handleMenuItem( int menuID, int position )
private boolean handleGameMenuItem( int menuID, long rowid )
{
boolean handled = true;
DialogInterface.OnClickListener lstnr;
m_rowid = DBUtils.gamesList( this )[position];
m_rowid = rowid;
if ( R.id.list_item_delete == menuID ) {
showConfirmThen( R.string.confirm_delete, R.string.button_delete,
@ -638,7 +761,13 @@ public class GamesList extends XWListActivity
case R.id.list_item_rename:
showDialog( RENAME_GAME );
break;
case R.id.list_item_move:
if ( 1 >= m_adapter.getGroupCount() ) {
showOKOnlyDialog( R.string.no_move_onegroup );
} else {
showDialog( CHANGE_GROUP );
}
break;
case R.id.list_item_new_from:
showNotAgainDlgThen( R.string.not_again_newfrom,
R.string.key_notagain_newfrom,
@ -674,7 +803,48 @@ public class GamesList extends XWListActivity
}
return handled;
} // handleMenuItem
} // handleGameMenuItem
private boolean handleGroupMenuItem( int menuID, int groupPos )
{
boolean handled = true;
m_groupid = m_adapter.getGroupIDFor( groupPos );
switch ( menuID ) {
case R.id.list_group_delete:
if ( m_groupid == XWPrefs.getDefaultNewGameGroup( this ) ) {
showOKOnlyDialog( R.string.cannot_delete_default_group );
} else {
String msg = getString( R.string.group_confirm_del );
int nGames = m_adapter.getChildrenCount( groupPos );
if ( 0 < nGames ) {
msg += getString( R.string.group_confirm_delf, nGames );
}
showConfirmThen( msg, DELETE_GROUP_ACTION );
}
break;
case R.id.list_group_rename:
showDialog( RENAME_GROUP );
break;
case R.id.list_group_default:
XWPrefs.setDefaultNewGameGroup( this, m_groupid );
break;
case R.id.list_group_moveup:
if ( m_adapter.moveGroup( m_groupid, -1 ) ) {
onContentChanged();
}
break;
case R.id.list_group_movedown:
if ( m_adapter.moveGroup( m_groupid, 1 ) ) {
onContentChanged();
}
break;
default:
handled = false;
}
return handled;
}
private boolean checkWarnNoDict( NetLaunchInfo nli )
{
@ -856,6 +1026,23 @@ public class GamesList extends XWListActivity
}
}
private Dialog buildNamerDlg( String curname, int labelID, int titleID,
DialogInterface.OnClickListener lstnr,
int dlgID )
{
m_namer = (GameNamer)Utils.inflate( this, R.layout.rename_game );
m_namer.setName( curname );
m_namer.setLabel( labelID );
Dialog dialog = new AlertDialog.Builder( this )
.setTitle( titleID )
.setNegativeButton( R.string.button_cancel, null )
.setPositiveButton( R.string.button_ok, lstnr )
.setView( m_namer )
.create();
Utils.setRemoveOnDismiss( this, dialog, dlgID );
return dialog;
}
private boolean makeNewNetGameIf()
{
boolean madeGame = null != m_netLaunchInfo;

View file

@ -23,28 +23,22 @@ package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.DialerKeyListener;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.ImageButton;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import junit.framework.Assert;
public class SMSInviteActivity extends InviteActivity {
@ -251,31 +245,30 @@ public class SMSInviteActivity extends InviteActivity {
// long time to return. Be safe.
if ( null != cursor && !cursor.isClosed() ) {
if ( cursor.moveToFirst() ) {
String name =
cursor.getString( cursor.
getColumnIndex( Phone.DISPLAY_NAME));
String number =
cursor.getString( cursor.
getColumnIndex( Phone.NUMBER ) );
String name =
cursor.getString( cursor.
getColumnIndex( Phone.DISPLAY_NAME));
String number =
cursor.getString( cursor.
getColumnIndex( Phone.NUMBER ) );
int type = cursor.getInt( cursor.
getColumnIndex( Phone.TYPE ) );
m_pendingName = name;
m_pendingNumber = number;
if ( Phone.TYPE_MOBILE == type ) {
showConfirmThen( R.string.warn_unlimited,
R.string.button_yes,
POST_WARNING_ACTION );
} else {
m_immobileConfirmed = false;
String msg =
Utils.format( this, R.string.warn_nomobilef,
number, name );
showConfirmThen( msg, R.string.button_yes,
USE_IMMOBILE_ACTION );
}
int type = cursor.getInt( cursor.
getColumnIndex( Phone.TYPE ) );
m_pendingName = name;
m_pendingNumber = number;
if ( Phone.TYPE_MOBILE == type ) {
showConfirmThen( R.string.warn_unlimited,
R.string.button_yes,
POST_WARNING_ACTION );
} else {
m_immobileConfirmed = false;
String msg =
Utils.format( this, R.string.warn_nomobilef,
number, name );
showConfirmThen( msg, R.string.button_yes,
USE_IMMOBILE_ACTION );
}
}
cursor.close();
}
} // addPhoneNumbers

View file

@ -353,6 +353,12 @@ public class Utils {
item.setVisible( enabled );
}
public static void setItemEnabled( Menu menu, int id, boolean enabled )
{
MenuItem item = menu.findItem( id );
item.setEnabled( enabled );
}
public static boolean hasSmallScreen( Context context )
{
if ( null == s_hasSmallScreen ) {

View file

@ -0,0 +1,129 @@
/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
/*
* Copyright 2010 - 2012 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.
*/
package org.eehouse.android.xw4;
import android.app.Dialog;
import android.app.ExpandableListActivity;
import android.content.DialogInterface;
import android.os.Bundle;
import junit.framework.Assert;
public class XWExpandableListActivity extends ExpandableListActivity
implements DlgDelegate.DlgClickNotify, MultiService.MultiEventListener {
private DlgDelegate m_delegate;
@Override
protected void onCreate( Bundle savedInstanceState )
{
DbgUtils.logf( "%s.onCreate(this=%H)", getClass().getName(), this );
super.onCreate( savedInstanceState );
m_delegate = new DlgDelegate( this, this, savedInstanceState );
}
@Override
protected void onSaveInstanceState( Bundle outState )
{
super.onSaveInstanceState( outState );
m_delegate.onSaveInstanceState( outState );
}
@Override
protected Dialog onCreateDialog( final int id )
{
DbgUtils.logf( "%s.onCreateDialog() called", getClass().getName() );
Dialog dialog = m_delegate.onCreateDialog( id );
if ( null == dialog ) {
dialog = super.onCreateDialog( id );
}
return dialog;
}
@Override
protected void onPrepareDialog( int id, Dialog dialog )
{
super.onPrepareDialog( id, dialog );
m_delegate.onPrepareDialog( id, dialog );
}
protected boolean post( Runnable runnable )
{
return m_delegate.post( runnable );
}
protected void doSyncMenuitem()
{
m_delegate.doSyncMenuitem();
}
protected void showNotAgainDlgThen( int msgID, int prefsKey,
int action )
{
m_delegate.showNotAgainDlgThen( msgID, prefsKey, action );
}
protected void showNotAgainDlg( int msgID, int prefsKey )
{
m_delegate.showNotAgainDlgThen( msgID, prefsKey );
}
// It sucks that these must be duplicated here and XWActivity
protected void showAboutDialog()
{
m_delegate.showAboutDialog();
}
protected void showOKOnlyDialog( int msgID )
{
m_delegate.showOKOnlyDialog( msgID );
}
protected void showConfirmThen( String msg, int action )
{
m_delegate.showConfirmThen( msg, action );
}
protected void showConfirmThen( String msg, int posButton, int action )
{
m_delegate.showConfirmThen( msg, posButton, action );
}
protected void showConfirmThen( int msg, int posButton, int action )
{
m_delegate.showConfirmThen( getString(msg), posButton, action );
}
// DlgDelegate.DlgClickNotify interface
public void dlgButtonClicked( int id, int which )
{
Assert.fail();
}
// BTService.BTEventListener interface
public void eventOccurred( MultiService.MultiEvent event,
final Object ... args )
{
m_delegate.eventOccurred( event, args );
}
}

View file

@ -105,12 +105,6 @@ public class XWListActivity extends ListActivity
m_delegate.onPrepareDialog( id, dialog );
}
// It sucks that these must be duplicated here and XWActivity
protected void showAboutDialog()
{
m_delegate.showAboutDialog();
}
protected void showNotAgainDlgThen( int msgID, int prefsKey,
int action )
{

View file

@ -26,7 +26,6 @@ import android.preference.PreferenceManager;
import android.text.TextUtils;
import com.google.android.gcm.GCMRegistrar;
import java.util.ArrayList;
import java.util.ArrayList;
public class XWPrefs {
@ -305,6 +304,29 @@ public class XWPrefs {
setPrefsLong( context, R.string.key_default_group, val );
}
public static void setGroupPositions( Context context, long[] posns )
{
String[] asStrs = new String[posns.length];
for ( int ii = 0; ii < posns.length; ++ii ) {
asStrs[ii] = String.format( "%d", posns[ii] );
}
setPrefsStringArray( context, R.string.key_group_posns, asStrs );
}
public static long[] getGroupPositions( Context context )
{
long[] posns = null;
String[] longStrs = getPrefsStringArray( context,
R.string.key_group_posns );
if ( null != longStrs ) {
posns = new long[longStrs.length];
for ( int ii = 0; ii < longStrs.length; ++ii ) {
posns[ii] = Long.parseLong(longStrs[ii]);
}
}
return posns;
}
protected static String getPrefsString( Context context, int keyID )
{
String key = context.getString( keyID );

View file

@ -224,9 +224,9 @@ public class GameSummary {
&& serverRole != DeviceRole.SERVER_STANDALONE );
}
private boolean isLocal( int indx ) {
int flag = 2 << (indx * 2);
return 0 == (m_giFlags & flag);
private boolean isLocal( int indx )
{
return localTurnNextImpl( m_giFlags, indx );
}
private boolean isRobot( int indx ) {
@ -328,4 +328,19 @@ public class GameSummary {
return String.format( "%s%s%s", separator, list, separator );
}
private static boolean localTurnNextImpl( int flags, int turn )
{
int flag = 2 << (turn * 2);
return 0 == (flags & flag);
}
public static Boolean localTurnNext( int flags, int turn )
{
Boolean result = null;
if ( 0 <= turn ) {
result = new Boolean( localTurnNextImpl( flags, turn ) );
}
return result;
}
}

View file

@ -292,7 +292,7 @@ public class JNIThread extends Thread {
}
byte[] state = XwJNI.game_saveToStream( m_jniGamePtr, m_gi );
if ( Arrays.equals( m_gameAtStart, state ) ) {
DbgUtils.logf( "no change in game; can skip saving" );
// DbgUtils.logf( "no change in game; can skip saving" );
} else {
GameSummary summary = new GameSummary( m_context, m_gi );
XwJNI.game_summarize( m_jniGamePtr, summary );

View file

@ -743,12 +743,14 @@ warnBadWords( const XP_UCHAR* word, XP_Bool isLegal,
{
XP_Bool ok = XP_TRUE;
if ( !isLegal ) {
BadWordInfo bwi;
BadWordInfo bwi = {0};
BoardCtxt* board = (BoardCtxt*)closure;
XP_S16 turn = server_getCurrentTurn( board->server );
bwi.nWords = 1;
bwi.words[0] = word;
bwi.dictName =
dict_getShortName( model_getPlayerDict( board->model, turn ) );
ok = !board->badWordRejected
&& util_warnIllegalWord( board->util, &bwi, turn, XP_FALSE );

View file

@ -82,7 +82,7 @@ typedef struct PickInfo {
typedef struct BadWordInfo {
XP_U16 nWords;
XP_UCHAR* dictName;
const XP_UCHAR* dictName;
const XP_UCHAR* words[MAX_TRAY_TILES+1]; /* can form in both directions */
} BadWordInfo;

View file

@ -1708,8 +1708,8 @@ gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player,
result = XP_TRUE;
} else {
XP_ASSERT( bwi->nWords == 1 );
sprintf( buf, "Word \"%s\" not in the current dictionary. "
"Use it anyway?", bwi->words[0] );
sprintf( buf, "Word \"%s\" not in the current dictionary (%s). "
"Use it anyway?", bwi->words[0], bwi->dictName );
result = gtkask( globals->window, buf, GTK_BUTTONS_YES_NO );
}