add filtering to wordlist browser

Add a basic regular expression engine to the dictiter, and to the UI add
the ability to filter for "starts with", "contains" and "ends with",
which translate into ANDed RE_*, _*RE_* and _*RE, respectively (with
_ standing for blank/wildcard). The engine's tightly integrated with the
next/prevWord() functions for greatest possible speed, but unless
there's no pattern does slow things down a bit (especially when "ENDS
WITH" is used.) The full engine is not exposed (users can't provide raw
REs), and while the parser will accept nesting (e.g. ([AB]_*[CD]){2,5}
to mean words from 2-5 tiles long starting with A or B and ending with C
or D) the engine can't handle it. Which is why filtering for word length
is handled separately from REs (but also tightly integrated.)

Users can enter strings that don't map to tiles. They now get an
error. It made sense for the error alert to have a "Show tiles"
button, so there's now a dialog listing all the tiles in a wordlist,
something the browser has needed all along.
This commit is contained in:
Eric House 2020-08-05 09:25:33 -07:00
parent d71cf6cac6
commit b8f359c3e5
66 changed files with 3446 additions and 1167 deletions

View file

@ -146,7 +146,6 @@ public class BoardCanvas extends Canvas implements DrawCtx {
m_context = context;
m_activity = activity;
m_jniThread = jniThread;
m_dict = new DictWrapper();
m_hasSmallScreen = Utils.hasSmallScreen( m_context );
@ -583,7 +582,7 @@ public class BoardCanvas extends Canvas implements DrawCtx {
@Override
public void dictChanged( final long newPtr )
{
long curPtr = m_dict.getDictPtr();
long curPtr = null == m_dict ? 0 : m_dict.getDictPtr();
boolean doPost = false;
if ( curPtr != newPtr ) {
if ( 0 == newPtr ) {
@ -595,7 +594,9 @@ public class BoardCanvas extends Canvas implements DrawCtx {
m_dictChars = null;
doPost = true;
}
m_dict.release();
if ( null != m_dict ) {
m_dict.release();
}
m_dict = new DictWrapper( newPtr );
}

View file

@ -122,15 +122,6 @@ public class DBUtils {
int ts;
}
public static class DictBrowseState {
public int m_minShown;
public int m_maxShown;
public int m_pos;
public int m_top;
public String m_prefix;
public int[] m_counts;
}
public static GameSummary getSummary( Context context,
GameLock lock )
{
@ -1951,86 +1942,6 @@ public class DBUtils {
return success;
}
/////////////////////////////////////////////////////////////////
// DictsDB stuff
/////////////////////////////////////////////////////////////////
public static DictBrowseState dictsGetOffset( Context context, String name,
DictLoc loc )
{
Assert.assertTrue( DictLoc.UNKNOWN != loc );
DictBrowseState result = null;
String[] columns = { DBHelper.ITERPOS, DBHelper.ITERTOP,
DBHelper.ITERMIN, DBHelper.ITERMAX,
DBHelper.WORDCOUNTS, DBHelper.ITERPREFIX };
String selection =
String.format( NAMELOC_FMT, DBHelper.DICTNAME,
name, DBHelper.LOC, loc.ordinal() );
initDB( context );
synchronized( s_dbHelper ) {
Cursor cursor = query( TABLE_NAMES.DICTBROWSE, columns, selection );
if ( 1 >= cursor.getCount() && cursor.moveToFirst() ) {
result = new DictBrowseState();
result.m_pos = cursor.getInt( cursor
.getColumnIndex(DBHelper.ITERPOS));
result.m_top = cursor.getInt( cursor
.getColumnIndex(DBHelper.ITERTOP));
result.m_minShown =
cursor.getInt( cursor
.getColumnIndex(DBHelper.ITERMIN));
result.m_maxShown =
cursor.getInt( cursor
.getColumnIndex(DBHelper.ITERMAX));
result.m_prefix =
cursor.getString( cursor
.getColumnIndex(DBHelper.ITERPREFIX));
String counts =
cursor.getString( cursor.getColumnIndex(DBHelper.WORDCOUNTS));
if ( null != counts ) {
String[] nums = TextUtils.split( counts, ":" );
int[] ints = new int[nums.length];
for ( int ii = 0; ii < nums.length; ++ii ) {
ints[ii] = Integer.parseInt( nums[ii] );
}
result.m_counts = ints;
}
}
cursor.close();
}
return result;
}
public static void dictsSetOffset( Context context, String name,
DictLoc loc, DictBrowseState state )
{
Assert.assertTrue( DictLoc.UNKNOWN != loc );
String selection =
String.format( NAMELOC_FMT, DBHelper.DICTNAME,
name, DBHelper.LOC, loc.ordinal() );
ContentValues values = new ContentValues();
values.put( DBHelper.ITERPOS, state.m_pos );
values.put( DBHelper.ITERTOP, state.m_top );
values.put( DBHelper.ITERMIN, state.m_minShown );
values.put( DBHelper.ITERMAX, state.m_maxShown );
values.put( DBHelper.ITERPREFIX, state.m_prefix );
if ( null != state.m_counts ) {
String[] nums = new String[state.m_counts.length];
for ( int ii = 0; ii < nums.length; ++ii ) {
nums[ii] = String.format( "%d", state.m_counts[ii] );
}
values.put( DBHelper.WORDCOUNTS, TextUtils.join( ":", nums ) );
}
initDB( context );
synchronized( s_dbHelper ) {
int result = update( TABLE_NAMES.DICTBROWSE, values, selection );
if ( 0 == result ) {
values.put( DBHelper.DICTNAME, name );
values.put( DBHelper.LOC, loc.ordinal() );
insert( TABLE_NAMES.DICTBROWSE, values );
}
}
}
// Called from jni
public static String dictsGetMD5Sum( Context context, String name )
{

View file

@ -592,6 +592,11 @@ public class DelegateBase implements DlgClickNotify,
m_dlgDelegate.startProgress( titleID, msg, null );
}
protected void startProgress( String title, String msg )
{
m_dlgDelegate.startProgress( title, msg, null );
}
protected void startProgress( int titleID, int msgID,
OnCancelListener lstnr )
{

View file

@ -25,9 +25,10 @@ import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
@ -39,39 +40,99 @@ import android.widget.SectionIndexer;
import android.widget.Spinner;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.ExpandImageButton.ExpandChangeListener;
import org.eehouse.android.xw4.jni.DictInfo;
import org.eehouse.android.xw4.jni.JNIUtilsImpl;
import org.eehouse.android.xw4.jni.XwJNI.DictWrapper;
import org.eehouse.android.xw4.jni.XwJNI.IterWrapper;
import org.eehouse.android.xw4.jni.XwJNI.PatDesc;
import org.eehouse.android.xw4.jni.XwJNI;
import java.util.Arrays;
import java.io.Serializable;
public class DictBrowseDelegate extends DelegateBase
implements View.OnClickListener, OnItemSelectedListener {
implements View.OnClickListener {
private static final String TAG = DictBrowseDelegate.class.getSimpleName();
private static final String DELIM = ".";
private static final boolean SHOW_NUM = false;
private static final String DICT_NAME = "DICT_NAME";
private static final String DICT_LOC = "DICT_LOC";
private static final int MIN_LEN = 2;
private static final int MAX_LEN = 15;
// Struct to show both what user's configuring AND what's been
// successfully fed to create the current iterator. The config setting
// become the filter params when the user presses the Apply Filter button
// and corrects any tile problems.
private static class DictBrowseState implements Serializable {
public int m_chosenMin, m_chosenMax;
public int m_passedMin, m_passedMax;
public int m_pos;
public int m_top;
public PatDesc[] m_pats;
public int[] m_counts;
public boolean m_expanded;
public DictBrowseState()
{
m_chosenMin = MIN_LEN;
m_chosenMax = MAX_LEN;
m_pats = new PatDesc[3];
for ( int ii = 0; ii < m_pats.length; ++ii ) {
m_pats[ii] = new PatDesc();
}
}
private void onFilterAccepted( DictWrapper dict, String delim )
{
m_passedMin = m_chosenMin;
m_passedMax = m_chosenMax;
for ( PatDesc desc : m_pats ) {
String str = XwJNI.dict_tilesToStr( dict, desc.tilePat, delim );
desc.strPat = str;
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder("{pats:[");
for ( PatDesc pd : m_pats ) {
sb.append(pd).append(",");
}
sb.append("],");
sb.append( "passedMin:").append(m_passedMin).append(",")
.append( "passedMax:").append(m_passedMax).append(",")
.append( "chosenMin:").append(m_chosenMin).append(",")
.append( "chosenMax:").append(m_chosenMax).append(",")
;
sb.append("}");
return sb.toString();
}
}
private Activity m_activity;
private long m_dictClosure = 0L;
private int m_lang;
private String m_name;
private DictUtils.DictLoc m_loc;
private DBUtils.DictBrowseState m_browseState;
private DictBrowseState m_browseState;
private int m_minAvail;
private int m_maxAvail;
private ListView m_list;
// - Steps to reproduce the problem:
// Create ListView, set custom adapter which implements ListAdapter and
// SectionIndexer but do not extends BaseAdapter. Enable fast scroll in
// layout. This will effect in ClassCastException.
private IterWrapper m_diClosure;
private DictWrapper m_dict;
private DictInfo mDictInfo;
private PatTableRow m_rows[] = { null, null, null };
private Spinner m_spinnerMin;
private Spinner m_spinnerMax;
private class DictListAdapter extends BaseAdapter
implements SectionIndexer {
@ -84,23 +145,19 @@ public class DictBrowseDelegate extends DelegateBase
{
super();
XwJNI.di_setMinMax( m_dictClosure, m_browseState.m_minShown,
m_browseState.m_maxShown );
m_nWords = XwJNI.di_wordCount( m_dictClosure );
int format = m_browseState.m_minShown == m_browseState.m_maxShown ?
R.string.dict_browse_title1_fmt : R.string.dict_browse_title_fmt;
setTitle( getString( format, m_name, m_nWords,
m_browseState.m_minShown,
m_browseState.m_maxShown ));
m_nWords = XwJNI.di_wordCount( m_diClosure );
Log.d( TAG, "making DictListAdapter; have %d words", m_nWords );
}
public Object getItem( int position )
{
TextView text = (TextView)
inflate( android.R.layout.simple_list_item_1 );
String str = XwJNI.di_nthWord( m_dictClosure, position, null );
String str = XwJNI.di_nthWord( m_diClosure, position, null );
if ( null != str ) {
if ( SHOW_NUM ) {
str = String.format( "%1$5d %2$s", position, str );
}
text.setText( str );
text.setOnClickListener( DictBrowseDelegate.this );
}
@ -114,7 +171,7 @@ public class DictBrowseDelegate extends DelegateBase
public long getItemId( int position ) { return position; }
public int getCount() {
Assert.assertTrue( 0 != m_dictClosure );
Assert.assertTrueNR( m_nWords == XwJNI.di_wordCount( m_diClosure ) );
return m_nWords;
}
@ -142,15 +199,16 @@ public class DictBrowseDelegate extends DelegateBase
@Override
public Object[] getSections()
{
m_prefixes = XwJNI.di_getPrefixes( m_dictClosure );
m_indices = XwJNI.di_getIndices( m_dictClosure );
m_prefixes = XwJNI.di_getPrefixes( m_diClosure );
m_indices = XwJNI.di_getIndices( m_diClosure );
return m_prefixes;
}
}
protected DictBrowseDelegate( Delegator delegator, Bundle savedInstanceState )
protected DictBrowseDelegate( Delegator delegator, Bundle sis )
{
super( delegator, savedInstanceState, R.layout.dict_browser );
super( delegator, sis, R.layout.dict_browser,
R.menu.dict_browse_menu );
m_activity = delegator.getActivity();
}
@ -168,71 +226,63 @@ public class DictBrowseDelegate extends DelegateBase
DictUtils.DictLoc.values()[args.getInt( DICT_LOC, 0 )];
m_lang = DictLangCache.getDictLangCode( m_activity, name );
String[] names = { name };
DictUtils.DictPairs pairs = DictUtils.openDicts( m_activity, names );
m_dictClosure = XwJNI.di_init( pairs.m_bytes[0],
name, pairs.m_paths[0] );
findTableRows();
m_spinnerMin = ((LabeledSpinner)findViewById( R.id.spinner_min ))
.getSpinner();
m_spinnerMax = ((LabeledSpinner)findViewById( R.id.spinner_max ))
.getSpinner();
String desc = XwJNI.di_getDesc( m_dictClosure );
Log.d( TAG, "got desc: %s", desc );
loadBrowseState();
String[] names = { m_name };
DictUtils.DictPairs pairs = DictUtils.openDicts( m_activity, names );
Assert.assertNotNull( m_browseState );
m_dict = XwJNI.makeDict( pairs.m_bytes[0], m_name, pairs.m_paths[0] );
mDictInfo = new DictInfo();
XwJNI.dict_getInfo( m_dict, false, mDictInfo );
setTitle( getString( R.string.dict_browse_title_fmt, m_name, mDictInfo.wordCount ) );
ExpandImageButton eib = (ExpandImageButton)findViewById( R.id.expander );
eib.setOnExpandChangedListener( new ExpandChangeListener() {
@Override
public void expandedChanged( boolean nowExpanded )
{
m_browseState.m_expanded = nowExpanded;
setShowConfig();
}
} )
.setExpanded( m_browseState.m_expanded );
String desc = XwJNI.dict_getDesc( m_dict );
if ( null != desc ) {
TextView view = (TextView)findViewById( R.id.desc );
Assert.assertNotNull( view );
view.setVisibility( View.VISIBLE );
view.setText( desc );
}
m_browseState = DBUtils.dictsGetOffset( m_activity, name, m_loc );
boolean newState = null == m_browseState;
if ( newState ) {
m_browseState = new DBUtils.DictBrowseState();
m_browseState.m_pos = 0;
m_browseState.m_top = 0;
}
if ( null == m_browseState.m_counts ) {
m_browseState.m_counts = XwJNI.di_getCounts( m_dictClosure );
int[] ids = { R.id.button_useconfig, R.id.button_addBlank, };
for ( int id : ids ) {
findViewById( id ).setOnClickListener(this);
}
if ( null == m_browseState.m_counts ) {
// empty dict? Just close down for now. Later if
// this is extended to include tile info -- it should
// be -- then use an empty list elem and disable
// search/minmax stuff.
String msg = getString( R.string.alert_empty_dict_fmt, name );
makeOkOnlyBuilder(msg).setAction(Action.FINISH_ACTION).show();
} else {
figureMinMax( m_browseState.m_counts );
if ( newState ) {
m_browseState.m_minShown = m_minAvail;
m_browseState.m_maxShown = m_maxAvail;
}
Button button = (Button)findViewById( R.id.search_button );
button.setOnClickListener( new View.OnClickListener() {
public void onClick( View view )
{
findButtonClicked();
}
} );
setUpSpinners();
initList();
}
setShowConfig();
replaceIter( true );
}
} // init
protected void onPause()
{
if ( null != m_browseState // already saved?
&& null != m_list ) { // there are words? (don't NPE on empty dict)
m_browseState.m_pos = m_list.getFirstVisiblePosition();
View view = m_list.getChildAt( 0 );
m_browseState.m_top = (view == null) ? 0 : view.getTop();
m_browseState.m_prefix = getFindText();
DBUtils.dictsSetOffset( m_activity, m_name, m_loc, m_browseState );
m_browseState = null;
}
scrapeBrowseState();
storeBrowseState();
// if ( null != m_browseState ) {
// if ( null != m_list ) { // there are words? (don't NPE on empty dict)
// m_browseState.m_pos = m_list.getFirstVisiblePosition();
// View view = m_list.getChildAt( 0 );
// m_browseState.m_top = (view == null) ? 0 : view.getTop();
// }
// storeBrowseState();
// }
super.onPause();
}
@ -240,30 +290,12 @@ public class DictBrowseDelegate extends DelegateBase
protected void onResume()
{
super.onResume();
if ( null == m_browseState ) {
m_browseState = DBUtils.dictsGetOffset( m_activity, m_name, m_loc );
}
setFindText( m_browseState.m_prefix );
}
@Override
protected void onDestroy()
{
XwJNI.di_destroy( m_dictClosure );
m_dictClosure = 0;
}
// Just in case onDestroy didn't get called....
@Override
public void finalize()
{
Assert.assertTrueNR( m_dictClosure == 0 );
XwJNI.di_destroy( m_dictClosure );
try {
super.finalize();
} catch ( java.lang.Throwable err ){
Log.i( TAG, "%s", err.toString() );
}
loadBrowseState();
// if ( null == m_browseState ) {
// m_browseState = DBUtils.dictsGetOffset( m_activity, m_name, m_loc );
// here
// }
setFindPats( m_browseState.m_pats );
}
@Override
@ -276,7 +308,7 @@ public class DictBrowseDelegate extends DelegateBase
final byte[][] choices = (byte[][])params[0];
final String[] strs = new String[choices.length];
for ( int ii = 0; ii < choices.length; ++ii ) {
strs[ii] = XwJNI.di_tilesToStr( m_dictClosure, choices[ii], DELIM );
strs[ii] = XwJNI.dict_tilesToStr( m_dict, choices[ii], DELIM );
}
final int[] chosen = {0};
dialog = makeAlertBuilder()
@ -293,14 +325,26 @@ public class DictBrowseDelegate extends DelegateBase
public void onClick( DialogInterface dialog, int which )
{
if ( 0 <= chosen[0] ) {
byte[][] theOne = {choices[chosen[0]]};
showPrefix( theOne, DELIM );
Assert.failDbg();
}
}
} )
.setTitle( R.string.pick_tiles_title )
.create();
break;
case SHOW_TILES:
String info = (String)params[0];
View tilesView = inflate( R.layout.tiles_table );
addTileRows( tilesView, info );
String langName = DictLangCache.getLangName( m_activity, m_lang );
String title = getString( R.string.show_tiles_title_fmt, langName );
dialog = makeAlertBuilder()
.setView( tilesView )
.setPositiveButton( android.R.string.ok, null )
.setTitle( title )
.create();
break;
default:
dialog = super.makeDialog( alert, params );
break;
@ -308,48 +352,42 @@ public class DictBrowseDelegate extends DelegateBase
return dialog;
}
@Override
public boolean onOptionsItemSelected( MenuItem item )
{
boolean handled = true;
switch ( item.getItemId() ) {
case R.id.dicts_showtiles:
showTiles();
break;
default:
handled = false;
}
return handled;
}
//////////////////////////////////////////////////
// View.OnClickListener interface
//////////////////////////////////////////////////
@Override
public void onClick( View view )
{
TextView text = (TextView)view;
String[] words = { text.getText().toString() };
launchLookup( words, m_lang, true );
}
//////////////////////////////////////////////////
// AdapterView.OnItemSelectedListener interface
//////////////////////////////////////////////////
@Override
public void onItemSelected( AdapterView<?> parent, View view,
int position, long id )
{
TextView text = (TextView)view;
// null text seems to have generated at least one google play report
if ( null != text && null != m_browseState ) {
int newval = Integer.parseInt( text.getText().toString() );
switch ( parent.getId() ) {
case R.id.wordlen_min:
if ( newval != m_browseState.m_minShown ) {
setMinMax( newval, m_browseState.m_maxShown );
}
break;
case R.id.wordlen_max:
if ( newval != m_browseState.m_maxShown ) {
setMinMax( m_browseState.m_minShown, newval );
}
break;
}
switch ( view.getId() ) {
case R.id.button_useconfig:
useButtonClicked();
break;
case R.id.button_addBlank:
addBlankButtonClicked();
break;
default:
TextView text = (TextView)view;
String[] words = { text.getText().toString() };
launchLookup( words, m_lang, true );
break;
}
}
@Override
public void onNothingSelected( AdapterView<?> parent )
{
}
//////////////////////////////////////////////////
// DlgDelegate.DlgClickNotify interface
//////////////////////////////////////////////////
@ -362,6 +400,9 @@ public class DictBrowseDelegate extends DelegateBase
handled = true;
finish();
break;
case SHOW_TILES:
showTiles();
break;
default:
handled = super.onPosButton( action, params );
break;
@ -369,70 +410,209 @@ public class DictBrowseDelegate extends DelegateBase
return handled;
}
private void findButtonClicked()
private void scrapeBrowseState()
{
String text = getFindText();
if ( null != text && 0 < text.length() ) {
m_browseState.m_prefix = text;
Assert.assertTrueNR( null != m_browseState );
m_browseState.m_chosenMin = MIN_LEN + m_spinnerMin.getSelectedItemPosition();
m_browseState.m_chosenMax = MIN_LEN + m_spinnerMax.getSelectedItemPosition();
if ( null != m_list ) { // there are words? (don't NPE on empty dict)
m_browseState.m_pos = m_list.getFirstVisiblePosition();
View view = m_list.getChildAt( 0 );
m_browseState.m_top = (view == null) ? 0 : view.getTop();
}
byte[][] choices = XwJNI.di_strToTiles( m_dictClosure, text );
if ( null == choices || 0 == choices.length ) {
String msg = getString( R.string.no_tiles_exist, text, m_name );
makeOkOnlyBuilder( msg ).show();
} else if ( 1 == choices.length || !XwJNI.di_hasDuplicates(m_dictClosure) ) {
showPrefix( choices, null );
} else {
showDialogFragment( DlgID.CHOOSE_TILES, (Object)choices );
// Get the strings (not bytes) from the rows
for ( int ii = 0; ii < m_rows.length; ++ii ) {
m_rows[ii].getToDesc(m_browseState.m_pats[ii]);
// .updateFrom( desc );
}
}
private static final int[] sTileRowIDs = {R.id.face, R.id.count, R.id.value };
private void addTileRows( View view, String info )
{
ViewGroup table = view.findViewById( R.id.table );
if ( null != table ) {
String[] tiles = TextUtils.split( info, "\n" );
for ( String row : tiles ) {
String[] fields = TextUtils.split( row, "\t" );
if ( 3 == fields.length ) {
ViewGroup rowView = (ViewGroup)inflate( R.layout.tiles_row );
for ( int ii = 0; ii < sTileRowIDs.length; ++ii ) {
TextView tv = (TextView)rowView.findViewById( sTileRowIDs[ii] );
tv.setText( fields[ii] );
}
table.addView( rowView );
}
}
}
}
private String getFindText()
private void showTiles()
{
EditWClear edit = (EditWClear)findViewById( R.id.word_edit );
return edit.getText().toString();
String info = XwJNI.getTilesInfo( m_dict );
showDialogFragment( DlgID.SHOW_TILES, info );
}
private void setFindText( String text )
private String m_stateKey = null;
private String getStateKey()
{
EditWClear edit = (EditWClear)findViewById( R.id.word_edit );
edit.setText( text );
if ( null == m_stateKey ) {
m_stateKey = String.format( "KEY_%s_%d", m_name, m_loc.ordinal() );
}
return m_stateKey;
}
private void showPrefix( byte[][] prefix, String delim )
private void findTableRows()
{
if ( null != prefix && 0 < prefix.length && 0 < prefix[0].length ) {
int pos = XwJNI.di_getStartsWith( m_dictClosure, prefix );
if ( 0 <= pos ) {
m_list.setSelection( pos );
} else {
String text = XwJNI.di_tilesToStr( m_dictClosure, prefix[0], delim );
DbgUtils.showf( m_activity, R.string.dict_browse_nowords_fmt,
m_name, text );
ViewGroup table = (ViewGroup)findViewById( R.id.config );
int count = table.getChildCount();
int nFound = 0;
for ( int ii = 0; ii < count && nFound < m_rows.length; ++ii ) {
View child = table.getChildAt( ii );
if ( child instanceof PatTableRow ) {
m_rows[nFound++] = (PatTableRow)child;
}
}
Assert.assertTrueNR( nFound == m_rows.length );
}
private void loadBrowseState()
{
boolean newState = false;
if ( null == m_browseState ) {
Serializable obj = DBUtils.getSerializableFor( m_activity, getStateKey() );
if ( null != obj && obj instanceof DictBrowseState ) {
m_browseState = (DictBrowseState)obj;
if ( null == m_browseState.m_pats ) {
m_browseState = null;
}
}
if ( null == m_browseState ) {
m_browseState = new DictBrowseState();
}
}
Log.d( TAG, "loadBrowseState() => %s", m_browseState );
}
private void storeBrowseState()
{
if ( null != m_browseState ) {
DBUtils.setSerializableFor( m_activity, getStateKey(), m_browseState );
}
}
private void useButtonClicked()
{
scrapeBrowseState();
Log.d( TAG, "useButtonClicked(): m_browseState: %s", m_browseState );
boolean pending = false;
if ( m_browseState.m_chosenMin > m_browseState.m_chosenMax ) {
pending = true;
makeOkOnlyBuilder( R.string.error_min_gt_max ).show();
}
PatDesc[] pats = m_browseState.m_pats;
for ( int ii = 0; ii < pats.length && !pending; ++ii ) {
String strPat = pats[ii].strPat;
if ( null != strPat && 0 < strPat.length() ) {
byte[][] choices = XwJNI.dict_strToTiles( m_dict, strPat );
if ( null == choices || 0 == choices.length ) {
String langName = DictLangCache.getLangName( m_activity, m_lang );
String msg = getString( R.string.no_tiles_exist, strPat, langName );
makeOkOnlyBuilder( msg )
.setActionPair( Action.SHOW_TILES, R.string.show_tiles_button )
.show();
pending = true;
} else if ( 1 == choices.length
|| !XwJNI.dict_hasDuplicates( m_dict ) ) {
pats[ii].tilePat = choices[0];
} else {
showDialogFragment( DlgID.CHOOSE_TILES, (Object)choices );
pending = true;
}
} else {
pats[ii].tilePat = null;
}
}
if ( !pending ) {
storeBrowseState();
replaceIter( false );
}
}
private void addBlankButtonClicked()
{
boolean handled = false;
for ( PatTableRow row : m_rows ) {
handled = handled || row.addBlankToFocussed( "_" );
}
if ( !handled ) {
makeNotAgainBuilder( R.string.blank_button_expl,
R.string.key_na_addBlankButton )
.setTitle(null)
.show();
}
}
private void setShowConfig()
{
findViewById(R.id.config).setVisibility( m_browseState.m_expanded
? View.VISIBLE : View.GONE );
}
private void setFindPats( PatDesc[] descs )
{
if ( null != descs && descs.length == m_rows.length ) {
for ( int ii = 0; ii < m_rows.length; ++ii ) {
m_rows[ii].setFromDesc( descs[ii] );
}
}
setUpSpinners();
}
private String formatPats( PatDesc[] pats, String delim )
{
Assert.assertTrueNR( null != m_diClosure );
List<String> strs = new ArrayList<>();
for ( int ii = 0; ii < pats.length; ++ii ) {
PatDesc desc = pats[ii];
String str = desc.strPat;
if ( null == str && (ii == 0 || ii == pats.length - 1) ) {
str = "";
}
if ( null != str ) {
strs.add(str);
}
}
String result = TextUtils.join( "", strs );
// Log.d( TAG, "formatPats() => %s", result );
return result;
}
private void setMinMax( int min, int max )
{
// I can't make a second call to setListAdapter() work, nor does
// notifyDataSetChanged do anything toward refreshing the
// adapter/making it recognize a changed dataset. So, as a
// workaround, relaunch the activity with different parameters.
if ( m_browseState.m_minShown != min ||
m_browseState.m_maxShown != max ) {
// if ( m_browseState.m_minShown != min ||
// m_browseState.m_maxShown != max ) {
m_browseState.m_pos = 0;
m_browseState.m_top = 0;
m_browseState.m_minShown = min;
m_browseState.m_maxShown = max;
m_browseState.m_prefix = getFindText();
DBUtils.dictsSetOffset( m_activity, m_name, m_loc, m_browseState );
// m_browseState.m_pos = 0;
// m_browseState.m_top = 0;
// m_browseState.m_minShown = min;
// m_browseState.m_maxShown = max;
// m_browseState.m_pats = getFindText();
// DBUtils.dictsSetOffset( m_activity, m_name, m_loc, m_browseState );
setUpSpinners();
// setUpSpinners();
initList();
}
// initList();
// }
}
private void figureMinMax( int[] counts )
@ -448,55 +628,106 @@ public class DictBrowseDelegate extends DelegateBase
}
}
private void makeSpinnerAdapter( int resID, int min, int max, int cur )
private String[] m_nums;
private void makeSpinnerAdapter( Spinner spinner, int curVal )
{
Spinner spinner = (Spinner)findViewById( resID );
Assert.assertTrue( min <= max );
int sel = -1;
String[] nums = new String[max - min + 1];
for ( int ii = 0; ii < nums.length; ++ii ) {
int val = min + ii;
if ( val == cur ) {
sel = ii;
}
nums[ii] = String.format( "%d", min + ii );
}
ArrayAdapter<String> adapter = new
ArrayAdapter<String>( m_activity,
//android.R.layout.simple_spinner_dropdown_item,
android.R.layout.simple_spinner_item,
nums );
m_nums );
adapter.setDropDownViewResource( android.R.layout.
simple_spinner_dropdown_item );
spinner.setAdapter( adapter );
spinner.setSelection( sel );
spinner.setOnItemSelectedListener( this );
spinner.setSelection( curVal - MIN_LEN );
// spinner.setOnItemSelectedListener( this );
}
private void setUpSpinners()
{
// Min and max-length spinners. To avoid empty lists,
// don't allow min to exceed max. Do that by making the
// current max the largest min allowed, and the current
// min the smallest max allowed.
makeSpinnerAdapter( R.id.wordlen_min, m_minAvail,
m_browseState.m_maxShown, m_browseState.m_minShown );
makeSpinnerAdapter( R.id.wordlen_max, m_browseState.m_minShown,
m_maxAvail, m_browseState.m_maxShown );
if ( null == m_nums ) {
m_nums = new String[MAX_LEN - MIN_LEN + 1];
for ( int ii = MIN_LEN; ii <= MAX_LEN; ++ii ) {
m_nums[ii - MIN_LEN] = String.format( "%d", ii );
}
}
makeSpinnerAdapter( m_spinnerMin, m_browseState.m_chosenMin );
makeSpinnerAdapter( m_spinnerMax, m_browseState.m_chosenMax );
}
private void initList()
private FrameLayout removeList()
{
m_list = null;
FrameLayout parent = (FrameLayout)findViewById(R.id.list_container);
parent.removeAllViews();
return parent;
}
private void replaceIter( boolean useOldVals )
{
Assert.assertNotNull( m_browseState );
Assert.assertNotNull( m_dict );
int min = useOldVals ? m_browseState.m_passedMin : m_browseState.m_chosenMin;
int max = useOldVals ? m_browseState.m_passedMax : m_browseState.m_chosenMax;
String title = getString( R.string.filter_title_fmt, m_name );
String msg = getString( R.string.filter_progress_fmt, mDictInfo.wordCount );
startProgress( title, msg );
XwJNI.di_init( m_dict, m_browseState.m_pats, min, max,
new XwJNI.DictIterProcs() {
@Override
public void onIterReady( final IterWrapper wrapper )
{
runOnUiThread( new Runnable() {
@Override
public void run() {
stopProgress();
m_browseState.onFilterAccepted( m_dict, null );
initList( wrapper );
setFindPats( m_browseState.m_pats );
}
} );
}
} );
}
private void initList( IterWrapper newIter )
{
FrameLayout parent = removeList();
m_list = (ListView)inflate( R.layout.dict_browser_list );
m_list.setAdapter( new DictListAdapter() );
Assert.assertNotNull( m_browseState );
Assert.assertNotNull( m_dict );
m_diClosure = newIter;
DictListAdapter dla = new DictListAdapter();
m_list.setAdapter( dla );
m_list.setFastScrollEnabled( true );
m_list.setSelectionFromTop( m_browseState.m_pos, m_browseState.m_top );
parent.addView( m_list );
updateFilterString();
}
private void updateFilterString()
{
PatDesc[] pats = m_browseState.m_pats;
Assert.assertNotNull( pats );
String summary;
String pat = formatPats( pats, null );
int nWords = XwJNI.di_wordCount( m_diClosure );
int[] minMax = XwJNI.di_getMinMax( m_diClosure );
summary = getString( R.string.filter_sum_pat_fmt, pat,
minMax[0], minMax[1],
nWords );
TextView tv = (TextView)findViewById( R.id.filter_summary );
tv.setText( summary );
}
private static void launch( Delegator delegator, Bundle bundle )

View file

@ -90,6 +90,7 @@ public class DlgDelegate {
DELETE_DICT_ACTION,
UPDATE_DICTS_ACTION,
MOVE_CONFIRMED,
SHOW_TILES,
// Game configs
LOCKED_CHANGE_ACTION,
@ -198,7 +199,13 @@ public class DlgDelegate {
Builder setTitle( int strID )
{
mState.setTitle( strID );
mState.setTitle( getString(strID) );
return this;
}
Builder setTitle( String str )
{
mState.setTitle( str );
return this;
}
@ -407,6 +414,11 @@ public class DlgDelegate {
public void startProgress( int titleID, String msg, OnCancelListener lstnr )
{
String title = getString( titleID );
startProgress( title, msg, lstnr );
}
public void startProgress( String title, String msg, OnCancelListener lstnr )
{
m_progress = ProgressDialog.show( m_activity, title, msg, true, true );
if ( null != lstnr ) {

View file

@ -116,8 +116,8 @@ public class DlgDelegateAlert extends XWDialogFragment {
AlertDialog.Builder builder = LocUtils.makeAlertBuilder( context );
if ( 0 != state.m_titleId ) {
builder.setTitle( state.m_titleId );
if ( null != state.m_title ) {
builder.setTitle( state.m_title );
}
populateBuilder( context, state, builder );

View file

@ -69,6 +69,7 @@ public enum DlgID {
, GAMES_LIST_NAME_REMATCH
, ASK_DUP_PAUSE
, CHOOSE_TILES
, SHOW_TILES
;
private boolean m_addToStack;

View file

@ -46,7 +46,7 @@ public class DlgState implements Parcelable {
public int m_prefsNAKey;
// These can't be serialized!!!!
public Object[] m_params;
public int m_titleId;
public String m_title;
public DlgState( DlgID dlgID )
{
@ -79,8 +79,8 @@ public class DlgState implements Parcelable {
{ m_posButton = id; return this; }
public DlgState setNegButton( int id )
{ m_negButton = id; return this; }
public DlgState setTitle( int id )
{ m_titleId = id; return this; }
public DlgState setTitle( String title )
{ m_title = title; return this; }
@Override
public String toString()
@ -103,7 +103,7 @@ public class DlgState implements Parcelable {
.append(", pair ").append(m_pair)
.append(", pos: ").append(m_posButton)
.append(", neg: ").append(m_negButton)
.append(", title: ").append(m_titleId)
.append(", title: ").append(m_title)
.append(", params: [").append(params)
.append("]}")
.toString();
@ -124,14 +124,15 @@ public class DlgState implements Parcelable {
DlgState other = (DlgState)it;
result = other != null
&& m_id.equals(other.m_id)
&& ((null == m_msg) ? (null == other.m_msg) : m_msg.equals(other.m_msg))
&& TextUtils.equals( m_msg, other.m_msg)
&& m_posButton == other.m_posButton
&& m_negButton == other.m_negButton
&& m_action == other.m_action
&& ((null == m_pair) ? (null == other.m_pair) : m_pair.equals(other.m_pair))
&& m_prefsNAKey == other.m_prefsNAKey
&& Arrays.deepEquals( m_params, other.m_params )
&& m_titleId == other.m_titleId;
&& TextUtils.equals( m_title,other.m_title)
;
}
} else {
result = super.equals( it );
@ -164,7 +165,7 @@ public class DlgState implements Parcelable {
out.writeInt( m_negButton );
out.writeInt( null == m_action ? -1 : m_action.ordinal() );
out.writeInt( m_prefsNAKey );
out.writeInt( m_titleId );
out.writeString( m_title );
out.writeString( m_msg );
out.writeSerializable( m_params );
out.writeSerializable( m_pair );
@ -196,7 +197,7 @@ public class DlgState implements Parcelable {
int tmp = in.readInt();
Action action = 0 > tmp ? null : Action.values()[tmp];
int prefsKey = in.readInt();
int titleId = in.readInt();
String title = in.readString();
String msg = in.readString();
Object[] params = (Object[])in.readSerializable();
ActionPair pair = (ActionPair)in.readSerializable();
@ -206,7 +207,7 @@ public class DlgState implements Parcelable {
.setNegButton( negButton )
.setAction( action )
.setPrefsNAKey( prefsKey )
.setTitle(titleId)
.setTitle(title)
.setParams(params)
.setActionPair(pair)
;

View file

@ -19,9 +19,10 @@
package org.eehouse.android.xw4;
import android.widget.SearchView;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.EditText;
import android.widget.SearchView;
import java.util.HashSet;
import java.util.Set;
@ -31,6 +32,7 @@ public class EditWClear extends SearchView
private static final String TAG = EditWClear.class.getSimpleName();
private Set<TextWatcher> mWatchers;
private EditText mEdit;
public interface TextWatcher {
void onTextChanged( String newText );
@ -41,6 +43,12 @@ public class EditWClear extends SearchView
super( context, as );
}
@Override
protected void onFinishInflate()
{
mEdit = (EditText)Utils.getChildInstanceOf( this, EditText.class );
}
synchronized void addTextChangedListener( TextWatcher proc )
{
if ( null == mWatchers ) {
@ -60,6 +68,17 @@ public class EditWClear extends SearchView
return super.getQuery();
}
void insertBlank( String blank )
{
// I'm not confident I'll always be able to get the edittext, so to be
// safe....
if ( null == mEdit ) {
setQuery( getQuery() + blank, false );
} else {
mEdit.getText().insert(mEdit.getSelectionStart(), blank );
}
}
// from SearchView.OnQueryTextListener
@Override
public synchronized boolean onQueryTextChange( String newText )

View file

@ -0,0 +1,63 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2009 - 2020 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.util.AttributeSet;
import android.view.View;
import android.widget.ImageButton;
public class ExpandImageButton extends ImageButton {
private boolean m_expanded;
public interface ExpandChangeListener {
public void expandedChanged( boolean nowExpanded );
}
public ExpandImageButton( Context context, AttributeSet as )
{
super( context, as );
}
public ExpandImageButton setExpanded( boolean expanded )
{
m_expanded = expanded;
setImageResource( expanded ?
R.drawable.expander_ic_maximized :
R.drawable.expander_ic_minimized);
return this;
}
public ExpandImageButton setOnExpandChangedListener( final ExpandChangeListener listener )
{
setOnClickListener( new View.OnClickListener() {
@Override
public void onClick( View view )
{
m_expanded = ! m_expanded;
setExpanded( m_expanded );
listener.expandedChanged( m_expanded );
}
} );
return this;
}
}

View file

@ -377,8 +377,8 @@ public class GameConfigDelegate extends DelegateBase
} else {
dictLabel.setVisibility( View.GONE );
}
m_playerDictSpinner = (Spinner)
playerView.findViewById( R.id.dict_spinner );
m_playerDictSpinner = ((LabeledSpinner)playerView.findViewById( R.id.player_dict_spinner ))
.getSpinner();
if ( localOnlyGame() ) {
configDictSpinner( m_playerDictSpinner, m_gi.dictLang, m_gi.dictName(lp) );
} else {
@ -429,11 +429,6 @@ public class GameConfigDelegate extends DelegateBase
lp.password = Utils.getText( dialog, R.id.password_edit );
if ( localOnlyGame() ) {
{
Spinner spinner =
(Spinner)((Dialog)di).findViewById( R.id.dict_spinner );
Assert.assertTrue( m_playerDictSpinner == spinner );
}
int position = m_playerDictSpinner.getSelectedItemPosition();
SpinnerAdapter adapter = m_playerDictSpinner.getAdapter();
@ -475,9 +470,13 @@ public class GameConfigDelegate extends DelegateBase
findViewById( R.id.play_button ).setOnClickListener( this );
m_playerLayout = (LinearLayout)findViewById( R.id.player_list );
m_phoniesSpinner = (Spinner)findViewById( R.id.phonies_spinner );
m_boardsizeSpinner = (Spinner)findViewById( R.id.boardsize_spinner );
m_smartnessSpinner = (Spinner)findViewById( R.id.smart_robot );
m_phoniesSpinner = ((LabeledSpinner)findViewById( R.id.phonies_spinner ))
.getSpinner();
m_boardsizeSpinner = ((LabeledSpinner)findViewById( R.id.boardsize_spinner ))
.getSpinner();
m_smartnessSpinner = ((LabeledSpinner)findViewById( R.id.smart_robot ))
.getSpinner();
m_connLabel = (TextView)findViewById( R.id.conns_label );
} // init

View file

@ -43,7 +43,8 @@ import java.util.HashSet;
import java.util.concurrent.LinkedBlockingQueue;
public class GameListItem extends LinearLayout
implements View.OnClickListener, SelectableItem.LongClickHandler {
implements View.OnClickListener, SelectableItem.LongClickHandler,
ExpandImageButton.ExpandChangeListener {
private static final String TAG = GameListItem.class.getSimpleName();
private static final int SUMMARY_WAIT_MSECS = 1000;
@ -67,7 +68,7 @@ public class GameListItem extends LinearLayout
private boolean m_expanded, m_haveTurn, m_haveTurnLocal;
private long m_lastMoveTime;
private ImageButton m_expandButton;
private ExpandImageButton m_expandButton;
private Handler m_handler;
private GameSummary m_summary;
private SelectableItem m_cb;
@ -165,15 +166,6 @@ public class GameListItem extends LinearLayout
{
int id = view.getId();
switch ( id ) {
case R.id.expander:
m_expanded = !m_expanded;
DBUtils.setExpanded( m_rowid, m_expanded );
makeThumbnailIf( m_expanded );
showHide();
break;
case R.id.view_loaded:
toggleSelected();
break;
@ -189,12 +181,24 @@ public class GameListItem extends LinearLayout
}
}
// ExpandImageButton.ExpandChangeListener
@Override
public void expandedChanged( boolean nowExpanded )
{
m_expanded = nowExpanded;
DBUtils.setExpanded( m_rowid, m_expanded );
makeThumbnailIf( m_expanded );
showHide();
}
private void findViews()
{
m_hideable = (LinearLayout)findViewById( R.id.hideable );
m_name = (ExpiringTextView)findViewById( R.id.game_name );
m_expandButton = (ImageButton)findViewById( R.id.expander );
m_expandButton.setOnClickListener( this );
m_expandButton = (ExpandImageButton)findViewById( R.id.expander );
m_expandButton.setOnExpandChangedListener( this );
m_viewUnloaded = (TextView)findViewById( R.id.view_unloaded );
m_viewLoaded = findViewById( R.id.view_loaded );
m_viewLoaded.setOnClickListener( this );
@ -225,9 +229,7 @@ public class GameListItem extends LinearLayout
private void showHide()
{
m_expandButton.setImageResource( m_expanded ?
R.drawable.expander_ic_maximized :
R.drawable.expander_ic_minimized);
m_expandButton.setExpanded( m_expanded );
m_hideable.setVisibility( m_expanded? View.VISIBLE : View.GONE );
int vis = m_expanded && XWPrefs.getThumbEnabled( m_context )

View file

@ -0,0 +1,63 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2020 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.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
/**
* This class's purpose is to link a spinner with a textview that's its label
* such that clicking on the label is the same as clicking on the spinner.
*/
public class LabeledSpinner extends LinearLayout {
private Spinner mSpinner;
public LabeledSpinner( Context context, AttributeSet as ) {
super( context, as );
}
@Override
protected void onFinishInflate()
{
mSpinner = (Spinner)Utils.getChildInstanceOf( this, Spinner.class );
TextView tv = (TextView)Utils.getChildInstanceOf( this, TextView.class );
tv.setOnClickListener( new OnClickListener() {
@Override
public void onClick( View target )
{
mSpinner.performClick();
}
} );
}
public Spinner getSpinner()
{
Assert.assertNotNull( mSpinner );
return mSpinner;
}
}

View file

@ -0,0 +1,88 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2020 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.util.AttributeSet;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TableRow;
import org.eehouse.android.xw4.jni.XwJNI.PatDesc;
public class PatTableRow extends TableRow {
private static final String TAG = PatTableRow.class.getSimpleName();
private EditWClear mEdit;
private CheckBox mCheck;
public PatTableRow( Context context, AttributeSet as )
{
super( context, as );
}
public void getToDesc( PatDesc out )
{
getFields();
// PatDesc result = null;
String strPat = mEdit.getText().toString();
out.strPat = strPat;
out.anyOrderOk = mCheck.isChecked();
// if ( null != strPat && 0 < strPat.length() ) {
// result = new PatDesc();
// result.strPat = strPat;
// result.anyOrderOk = mCheck.isChecked();
// }
// return result;
}
public void setFromDesc( PatDesc desc )
{
getFields();
mEdit.setText(desc.strPat);
mCheck.setChecked(desc.anyOrderOk);
}
public boolean addBlankToFocussed( String blank )
{
getFields();
boolean handled = mEdit.hasFocus();
if ( handled ) {
mEdit.insertBlank( blank );
}
return handled;
}
private void getFields()
{
for ( int ii = 0;
(null == mEdit || null == mCheck) && ii < getChildCount();
++ii ) {
View view = getChildAt( ii );
if ( view instanceof EditWClear ) {
mEdit = (EditWClear)view;
} else if ( view instanceof CheckBox ) {
mCheck = (CheckBox)view;
}
}
}
}

View file

@ -59,7 +59,7 @@ public class StudyListDelegate extends ListDelegateBase
private Activity m_activity;
private Spinner m_spinner;
private View m_pickView; // LinearLayout, actually
private LabeledSpinner m_pickView;
private int[] m_langCodes;
private String[] m_words;
private Set<String> m_checkeds;
@ -79,8 +79,8 @@ public class StudyListDelegate extends ListDelegateBase
{
m_list = (ListView)findViewById( android.R.id.list );
m_spinner = (Spinner)findViewById( R.id.pick_lang_spinner );
m_pickView = findViewById( R.id.pick_lang );
m_pickView = (LabeledSpinner)findViewById( R.id.pick_lang );
m_spinner = m_pickView.getSpinner();
m_checkeds = new HashSet<>();
m_words = new String[0];

View file

@ -23,7 +23,6 @@ package org.eehouse.android.xw4;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.LinearLayout;
import android.widget.TextView;

View file

@ -48,6 +48,7 @@ import android.util.Base64;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
@ -690,6 +691,21 @@ public class Utils {
return Looper.getMainLooper().equals(Looper.myLooper());
}
public static View getChildInstanceOf( ViewGroup parent, Class clazz )
{
View result = null;
for ( int ii = 0; null == result && ii < parent.getChildCount(); ++ii ) {
View child = parent.getChildAt( ii );
if ( clazz.isInstance(child) ) {
result = child;
break;
} else if ( child instanceof ViewGroup ) {
result = getChildInstanceOf( (ViewGroup)child, clazz );
}
}
return result;
}
// But see hexArray above
private static final String HEX_CHARS = "0123456789ABCDEF";
private static char[] HEX_CHARS_ARRAY = HEX_CHARS.toCharArray();

View file

@ -22,6 +22,7 @@ package org.eehouse.android.xw4.jni;
import android.graphics.Rect;
import java.io.Serializable;
import java.util.Arrays;
import org.eehouse.android.xw4.Assert;
@ -531,29 +532,130 @@ public class XwJNI {
public static boolean dict_getInfo( byte[] dict, String name, String path,
boolean check, DictInfo info )
{
return dict_getInfo( getJNI().m_ptrGlobals, dict, name, path, check, info );
DictWrapper wrapper = makeDict( dict, name, path );
return dict_getInfo( wrapper, check, info );
}
public static boolean dict_getInfo( DictWrapper dict, boolean check, DictInfo info )
{
return dict_getInfo( getJNI().m_ptrGlobals, dict.getDictPtr(),
check, info );
}
public static String dict_getDesc( DictWrapper dict )
{
return dict_getDesc( dict.getDictPtr() );
}
public static String dict_tilesToStr( DictWrapper dict, byte[] tiles, String delim )
{
return dict_tilesToStr( dict.getDictPtr(), tiles, delim );
}
public static byte[][] dict_strToTiles( DictWrapper dict, String str )
{
return dict_strToTiles( dict.getDictPtr(), str );
}
public static boolean dict_hasDuplicates( DictWrapper dict )
{
return dict_hasDuplicates( dict.getDictPtr() );
}
public static String getTilesInfo( DictWrapper dict )
{
return dict_getTilesInfo( getJNI().m_ptrGlobals, dict.getDictPtr() );
}
public static native int dict_getTileValue( long dictPtr, int tile );
// Dict iterator
public final static int MAX_COLS_DICT = 15; // from dictiter.h
public static long di_init( byte[] dict, String name, String path )
public static DictWrapper makeDict( byte[] bytes, String name, String path )
{
return di_init( getJNI().m_ptrGlobals, dict, name, path );
long dict = dict_make( getJNI().m_ptrGlobals, bytes, name, path );
return new DictWrapper( dict );
}
public static native void di_setMinMax( long closure, int min, int max );
public static native void di_destroy( long closure );
public static native int di_wordCount( long closure );
public static native int[] di_getCounts( long closure );
public static native String di_nthWord( long closure, int nn, String delim );
public static native String[] di_getPrefixes( long closure );
public static native int[] di_getIndices( long closure );
public static native byte[][] di_strToTiles( long closure, String str );
public static native int di_getStartsWith( long closure, byte[][] prefix );
public static native String di_getDesc( long closure );
public static native String di_tilesToStr( long closure, byte[] tiles, String delim );
public static native boolean di_hasDuplicates( long closure );
public static class PatDesc implements Serializable {
public String strPat;
public byte[] tilePat;
public boolean anyOrderOk;
@Override
public String toString()
{
return String.format( "{str: %s; nTiles: %d; anyOrderOk: %b}",
strPat, null == tilePat ? 0 : tilePat.length,
anyOrderOk );
}
}
public static class IterWrapper {
private long iterRef;
private IterWrapper(long ref) { this.iterRef = ref; }
private long getRef() { return this.iterRef; }
@Override
public void finalize() throws java.lang.Throwable
{
di_destroy( iterRef );
super.finalize();
}
}
public interface DictIterProcs {
void onIterReady( IterWrapper iterRef );
}
public static void di_init( DictWrapper dict, final PatDesc[] pats,
final int minLen, final int maxLen,
final DictIterProcs callback )
{
final long jniState = getJNI().m_ptrGlobals;
final long dictPtr = dict.getDictPtr();
new Thread( new Runnable() {
@Override
public void run() {
long iterPtr = di_init( jniState, dictPtr, pats,
minLen, maxLen );
callback.onIterReady( new IterWrapper(iterPtr) );
}
} ).start();
}
public static int di_wordCount( IterWrapper iter )
{
return di_wordCount( iter.getRef() );
}
public static String di_nthWord( IterWrapper iter, int nn, String delim )
{
return di_nthWord( iter.getRef(), nn, delim );
}
public static int[] di_getMinMax( IterWrapper iter ) {
return di_getMinMax( iter.getRef() );
}
public static String[] di_getPrefixes( IterWrapper iter )
{
return di_getPrefixes( iter.getRef() );
}
public static int[] di_getIndices( IterWrapper iter )
{
return di_getIndices( iter.getRef() );
}
private static native void di_destroy( long closure );
private static native int di_wordCount( long closure );
private static native String di_nthWord( long closure, int nn, String delim );
private static native int[] di_getMinMax( long closure );
private static native String[] di_getPrefixes( long closure );
private static native int[] di_getIndices( long closure );
// Private methods -- called only here
private static native long initGlobals( DUtilCtxt dutil, JNIUtils jniu );
@ -575,14 +677,18 @@ public class XwJNI {
byte[] stream );
private static native long initGameJNI( long jniState, int seed );
private static native void envDone( long globals );
private static native long dict_make( long jniState, byte[] dict, String name, String path );
private static native void dict_ref( long dictPtr );
private static native void dict_unref( long dictPtr );
private static native boolean dict_getInfo( long jniState, byte[] dict,
String name, String path,
boolean check,
DictInfo info );
private static native long di_init( long jniState, byte[] dict,
String name, String path );
private static native byte[][] dict_strToTiles( long dictPtr, String str );
private static native String dict_tilesToStr( long dictPtr, byte[] tiles, String delim );
private static native boolean dict_hasDuplicates( long dictPtr );
private static native String dict_getTilesInfo( long jniState, long dictPtr );
private static native boolean dict_getInfo( long jniState, long dictPtr,
boolean check, DictInfo info );
private static native String dict_getDesc( long dictPtr );
private static native long di_init( long jniState, long dictPtr,
PatDesc[] pats, int minLen, int maxLen );
private static native byte[][]
smsproto_prepOutbound( long jniState, SMS_CMD cmd, int gameID, byte[] buf,

View file

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:focusableInTouchMode="true"
@ -13,73 +14,151 @@
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:visibility="gone"
android:layout_alignParentTop="true"
/>
<LinearLayout android:id="@+id/search"
android:orientation="horizontal"
<!-- A summary followed by a hideable filter-config section -->
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_below="@id/desc"
>
<!-- Here's the summary -->
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView android:id="@+id/filter_summary"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_weight="1"
/>
<org.eehouse.android.xw4.ExpandImageButton
android:id="@+id/expander"
style="@style/expander_button"
/>
</LinearLayout>
<org.eehouse.android.xw4.EditWClear
android:id="@+id/word_edit"
style="@style/edit_w_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:inputType="text"
android:layout_weight="1"
android:hint="@string/word_search_hint"
android:capitalize="characters"
/>
<Button android:id="@+id/search_button"
android:layout_width="wrap_content"
<!-- Config panel. A vertical array of fields with button at
bottom. Can be hidden -->
<TableLayout android:id="@+id/config"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="#FF1F1F1F"
android:visibility="gone"
android:paddingLeft="8dp"
android:paddingRight="8dp"
>
<TableRow
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<View android:layout_width="5dp"
android:layout_height="5dp"
android:layout_span="2"
/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pat_col_anyorder"
/>
</TableRow>
<org.eehouse.android.xw4.PatTableRow
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView style="@style/pat_table_label"
android:text="@string/pat_starts_with"
/>
<org.eehouse.android.xw4.EditWClear
style="@style/pat_table_edit"
/>
<CheckBox style="@style/pat_table_check"
/>
</org.eehouse.android.xw4.PatTableRow>
<org.eehouse.android.xw4.PatTableRow
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView style="@style/pat_table_label"
android:text="@string/pat_contains"
/>
<org.eehouse.android.xw4.EditWClear
style="@style/pat_table_edit"
/>
<CheckBox style="@style/pat_table_check"
/>
</org.eehouse.android.xw4.PatTableRow>
<org.eehouse.android.xw4.PatTableRow
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView style="@style/pat_table_label"
android:text="@string/pat_ends_with"
/>
<org.eehouse.android.xw4.EditWClear
style="@style/pat_table_edit"
/>
<CheckBox style="@style/pat_table_check"
/>
</org.eehouse.android.xw4.PatTableRow>
<TableRow android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<Button android:id="@+id/button_addBlank"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/button_addBlank"
android:layout_span="1"
/>
</TableRow>
<LinearLayout android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="horizontal"
>
<org.eehouse.android.xw4.LabeledSpinner
android:id="@+id/spinner_min"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:text="@string/button_search"
android:layout_weight="0"
/>
android:layout_width="wrap_content"
>
<TextView style="@style/config_spinner_label"
android:text="@string/min_len"
/>
<Spinner android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:drawSelectorOnTop="true"
android:label="@string/title_langs_list"
/>
</org.eehouse.android.xw4.LabeledSpinner>
<org.eehouse.android.xw4.LabeledSpinner
android:id="@+id/spinner_max"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
>
<TextView style="@style/config_spinner_label"
android:text="@string/max_len"
/>
<Spinner android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:drawSelectorOnTop="true"
android:label="@string/title_langs_list"
/>
</org.eehouse.android.xw4.LabeledSpinner>
</LinearLayout>
<Button android:id="@+id/button_useconfig"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/button_useconfig"
android:layout_gravity="center_horizontal"
android:layout_span="2"
/>
</TableLayout>
</LinearLayout>
<LinearLayout android:id="@+id/sizes"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_alignParentBottom="true"
>
<TextView android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/min_len"
/>
<Spinner android:id="@+id/wordlen_min"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
android:prompt="@string/prompt_min_len"
/>
<TextView android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/max_len"
/>
<Spinner android:id="@+id/wordlen_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
android:prompt="@string/prompt_max_len"
/>
</LinearLayout>
<!-- This goes in the middle but gets listed here because stuff it's
relative to must be defined first. -->
<FrameLayout android:id="@+id/list_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/search"
android:layout_above="@id/sizes"
android:layout_weight="1"
/>
</RelativeLayout>
</LinearLayout>

View file

@ -235,29 +235,30 @@
android:text="@string/hints_allowed"
/>
<LinearLayout style="@style/config_spinner_container"
>
<org.eehouse.android.xw4.LabeledSpinner
android:id="@+id/smart_robot"
style="@style/config_spinner_container"
>
<TextView style="@style/config_spinner_label"
android:text="@string/robot_iq_label"
/>
<Spinner style="@style/config_spinner_spinner"
android:id="@+id/smart_robot"
android:prompt="@string/robot_spinner_prompt"
android:entries="@array/robot_levels"
/>
</LinearLayout>
<LinearLayout style="@style/config_spinner_container"
>
</org.eehouse.android.xw4.LabeledSpinner>
<org.eehouse.android.xw4.LabeledSpinner
style="@style/config_spinner_container"
android:id="@+id/phonies_spinner"
>
<TextView style="@style/config_spinner_label"
android:text="@string/phonies_label"
/>
<Spinner style="@style/config_spinner_spinner"
android:id="@+id/phonies_spinner"
android:prompt="@string/phonies_spinner_prompt"
android:entries="@array/phony_names"
/>
</LinearLayout>
</org.eehouse.android.xw4.LabeledSpinner>
<CheckBox android:id="@+id/pick_faceup"
android:layout_width="fill_parent"
@ -265,17 +266,18 @@
android:text="@string/pick_faceup"
/>
<LinearLayout style="@style/config_spinner_container"
>
<org.eehouse.android.xw4.LabeledSpinner
style="@style/config_spinner_container"
android:id="@+id/boardsize_spinner"
>
<TextView style="@style/config_spinner_label"
android:text="@string/board_size"
/>
<Spinner style="@style/config_spinner_spinner"
android:id="@+id/boardsize_spinner"
android:prompt="@string/board_size"
android:entries="@array/board_sizes"
/>
</LinearLayout>
</org.eehouse.android.xw4.LabeledSpinner>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="fill_parent"

View file

@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- -*- compile-command: "cd ../../; ant install"; -*- -->
<!-- top-level layout is hozontal, with an image and another layout -->
<org.eehouse.android.xw4.GameListItem
xmlns:android="http://schemas.android.com/apk/res/android"
@ -99,9 +97,10 @@
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<ImageButton android:id="@+id/expander"
style="@style/expander_button"
/>
<org.eehouse.android.xw4.ExpandImageButton
android:id="@+id/expander"
style="@style/expander_button"
/>
</LinearLayout>

View file

@ -63,10 +63,13 @@
/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
>
<!-- And here -->
<org.eehouse.android.xw4.LabeledSpinner
android:id="@+id/player_dict_spinner"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
>
<TextView android:id="@+id/dict_label"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
@ -75,12 +78,11 @@
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<Spinner android:id="@+id/dict_spinner"
android:layout_width="fill_parent"
<Spinner android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
/>
</LinearLayout>
</org.eehouse.android.xw4.LabeledSpinner>
<CheckBox android:id="@+id/robot_check"
android:layout_width="wrap_content"

View file

@ -9,28 +9,26 @@
android:paddingRight="8dp"
>
<LinearLayout android:id="@+id/pick_lang"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/study_langpick"
/>
<Spinner android:id="@+id/pick_lang_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
/>
</LinearLayout>
<ListView android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:drawSelectorOnTop="false"
<org.eehouse.android.xw4.LabeledSpinner
android:id="@+id/pick_lang"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/study_langpick"
/>
<Spinner android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
/>
</org.eehouse.android.xw4.LabeledSpinner>
<ListView android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:drawSelectorOnTop="false"
/>
</LinearLayout>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView android:id="@+id/face"
style="@style/tiles_table_row"
/>
<TextView android:id="@+id/count"
style="@style/tiles_table_row"
/>
<TextView android:id="@+id/value"
style="@style/tiles_table_row"
/>
</TableRow>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp"
>
<TableRow android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
>
<TextView android:text="@string/tiles_header_face"
style="@style/tiles_table_row"
/>
<TextView android:text="@string/tiles_header_count"
style="@style/tiles_table_row"
/>
<TextView android:text="@string/tiles_header_value"
style="@style/tiles_table_row"
/>
</TableRow>
<ScrollView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<LinearLayout android:id="@+id/table"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</ScrollView>
</TableLayout>

View file

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

View file

@ -155,6 +155,7 @@
<string name="key_na_sms_banned">key_na_sms_banned</string>
<string name="key_na_longtap_lookup">key_na_longtap_lookup</string>
<string name="key_na_perms_phonestate">key_na_perms_phonestate</string>
<string name="key_na_addBlankButton">key_na_addBlankButton</string>
<string name="key_na_dupstatus_host">key_na_dupstatus_host</string>
<string name="key_na_dupstatus_guest">key_na_dupstatus_guest</string>

View file

@ -1539,10 +1539,52 @@
<!-- -->
<string name="button_move">Move</string>
<string name="button_newgroup">New group</string>
<!-- -->
<string name="button_search">Find</string>
<!-- -->
<string name="word_search_hint">First letters</string>
<!-- For user to confirm/apply wordlist filter -->
<string name="button_useconfig">Apply Filter</string>
<!-- Button in wordlist filter: keep it short!!!! -->
<string name="button_addBlank">+Blank</string>
<!-- String showing user what filter is in use. Substitutions are
1) the filter pattern, 2) minimum and 3) maximum lengths, and
4) number of words in the filtered list. -->
<string name="filter_sum_pat_fmt">Showing: %1$s, lengths %2$d-%3$d
(%4$d words)</string>
<!-- Shown when user "misspells" a pattern using a combination not
legal in the language, e.g. Q alone in Spanish or Catalan -->
<string name="show_tiles_button">Show Tiles</string>
<!-- Menuitem in dict browser -->
<string name="show_tiles_menu">\u200CShow Tiles</string>
<!-- title of dialog showing the list of tiles. Passed language
name, e.g. Tiles for English -->
<string name="show_tiles_title_fmt">Tiles for %1$s</string>
<!-- Shown when user uses the +Blank button in wordlist browser
but nothing can be done because there's not focussed text
field -->
<string name="blank_button_expl">This button inserts a blank at
the active text field\'s cursor, but there is no cursor
now.</string>
<!-- Label for first field in wordlist browser filter -->
<string name="pat_starts_with">Starts with</string>
<!-- Label for middle field in wordlist browser filter -->
<string name="pat_contains">Contains</string>
<!-- Label for last field in wordlist browser filter -->
<string name="pat_ends_with">Ends with</string>
<!-- Column head for checkboxes that cause the tiles to be
accepted in any order. E.g. "ends with: ING" will also accept
words ending in GIN if the box under this head is
checked-->
<string name="pat_col_anyorder">Any Order</string>
<!-- Headers for columns in tiles table: 1: letter on tile -->
<string name="tiles_header_face">Letter</string>
<!-- Headers for columns in tiles table: 2: Number of tiles with
that letter -->
<string name="tiles_header_count">Number</string>
<!-- Headers for columns in tiles table: 3: Value of each tile -->
<string name="tiles_header_value">Value</string>
<!-- -->
<string name="tilepick_all_fmt">Pick %1$d for me</string>
<!-- -->
@ -1550,14 +1592,7 @@
<!-- This is for debug builds only! A setting for testing networking -->
<string name="disableds_title">Disabled address types</string>
<!-- -->
<string name="dict_browse_title_fmt">%1$s (%2$d words using %3$d-%4$d
tiles)</string>
<!-- -->
<string name="dict_browse_title1_fmt">%1$s (%2$d words using %3$d
tiles)</string>
<!-- -->
<string name="dict_browse_nowords_fmt">No word in %1$s starts with
%2$s.</string>
<string name="dict_browse_title_fmt">%1$s (%2$d words total)</string>
<!-- -->
<string name="not_again_browse">This button opens the wordlist
browser on the current player\'s wordlist.</string>
@ -1576,6 +1611,17 @@
<!-- -->
<string name="prompt_max_len">Words no longer than</string>
<!-- -->
<!-- Shown when user tries to make a wordlist filter where max < min -->
<string name="error_min_gt_max">The minimum length value cannot be
greater than the maximum value.</string>
<!-- Title of progress alert shown while wordlist is loading. For
huge lists like Polish this can take a few seconds. -->
<string name="filter_title_fmt">Loading %1$s…</string>
<!-- Message in progress alert shown while wordlist is loading -->
<string name="filter_progress_fmt">Processing %1$d words</string>
<string name="board_menu_file_email">Email author</string>
<!-- -->
<string name="email_author_subject">Comment about CrossWords</string>
@ -2553,8 +2599,17 @@
<string name="logstore_clear_confirm">Are you sure you want to
erase your debug logs? This action cannot be undone.</string>
<!-- Shown when there's more than one sequence of tiles with which
to "spell" the string a user has entered in the wordlist
browser. Displayed below are the possible
spellings. Currently this can only happen with Hungarian
wordlists. -->
<string name="pick_tiles_title">Pick a tile \"spelling\"</string>
<string name="no_tiles_exist">\"%1$s\" cannot be spelled with tiles in %2$s.</string>
<!-- Shown when user types a string that contains non-tiles,
e.g. a Q alone when the langauge has only QU. First
substituted is user's string; second substitution is name of
language -->
<string name="no_tiles_exist">\"%1$s\" cannot be spelled with %2$s tiles.</string>
<!-- MQTT stuff. May not see the light of day -->
<string name="invite_choice_mqtt">Internet/MQTT</string>

View file

@ -112,6 +112,34 @@
<item name="android:searchHintIcon">@null</item>
</style>
<!-- Columns for the dict browser pattern editor -->
<style name="pat_table_label">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">center_vertical</item>
</style>
<style name="pat_table_edit" parent="edit_w_clear">
<item name="android:layout_width">200dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:maxLines">1</item>
<item name="android:inputType">text</item>
</style>
<style name="pat_table_check">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">center</item>
</style>
<style name="tiles_table_row">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
<item name="android:textSize">14dp</item>
<item name="android:gravity">center_horizontal</item>
</style>
<style name="config_spinner_container">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>

View file

@ -27,6 +27,7 @@
#include "comtypes.h"
#include "xwstream.h"
#include "strutils.h"
#include "dbgutil.h"
void
and_assert( const char* test, int line, const char* file, const char* func )
@ -268,10 +269,10 @@ getObject( JNIEnv* env, jobject obj, const char* name, const char* sig,
jfieldID fid = (*env)->GetFieldID( env, cls, name, sig );
XP_ASSERT( !!fid );
*ret = (*env)->GetObjectField( env, obj, fid );
XP_ASSERT( !!*ret );
bool result = !!*ret;
deleteLocalRef( env, cls );
return true;
return result;
}
void

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2001 - 2017 by Eric House (xwords@eehouse.org). All rights
* Copyright 2001 - 2020 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@ -398,7 +398,7 @@ and_util_makeEmptyDict( XW_UtilCtxt* uc, XWEnv xwe )
DictionaryCtxt* result =
and_dictionary_make_empty( MPPARM( ((AndUtil*)uc)->util.mpool )
globals->jniutil );
return dict_ref( result, xwe );
return (DictionaryCtxt*)dict_ref( result, xwe );
#endif
}

View file

@ -879,6 +879,23 @@ Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getUUID
return jstr;
}
JNIEXPORT jlong JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1make
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jbyteArray jDictBytes,
jstring jname, jstring jpath )
{
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
#ifdef MEM_DEBUG
MemPoolCtx* mpool = GETMPOOL( globalState );
#endif
/* makeDict calls dict_ref() */
DictionaryCtxt* dictPtr = makeDict( MPPARM(mpool) env, TI_IF(&globalState->ti)
globalState->dictMgr,globalState->jniutil,
jname, jDictBytes, jpath, NULL, false );
return (jlong)dictPtr;
}
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1ref
( JNIEnv* env, jclass C, jlong dictPtr )
@ -899,36 +916,141 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1unref
}
}
JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jbyteArray jDictBytes,
jstring jname, jstring jpath, jboolean check, jobject jinfo )
{
jboolean result = false;
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
MAP_THREAD( &globalState->ti, env );
typedef struct _FTData {
JNIEnv* env;
jbyteArray arrays[16];
int nArrays;
} FTData;
static XP_Bool
onFoundTiles( void* closure, const Tile* tiles, int nTiles )
{
FTData* ftd = (FTData*)closure;
ftd->arrays[ftd->nArrays++] = makeByteArray( ftd->env, nTiles,
(const jbyte*)tiles );
return ftd->nArrays < VSIZE(ftd->arrays); /* still have room? */
}
JNIEXPORT jobjectArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1strToTiles
( JNIEnv* env, jclass C, jlong dictPtr, jstring jstr )
{
jobjectArray result = NULL;
DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr;
const char* str = (*env)->GetStringUTFChars( env, jstr, NULL );
FTData ftd = { .env = env, };
dict_tilesForString( dict, str, 0, onFoundTiles, &ftd );
if ( ftd.nArrays > 0 ) {
result = makeByteArrayArray( env, ftd.nArrays );
for ( int ii = 0; ii < ftd.nArrays; ++ii ) {
(*env)->SetObjectArrayElement( env, result, ii, ftd.arrays[ii] );
deleteLocalRef( env, ftd.arrays[ii] );
}
}
(*env)->ReleaseStringUTFChars( env, jstr, str );
return result;
}
JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1hasDuplicates
( JNIEnv* env, jclass C, jlong dictPtr )
{
jboolean result;
DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr;
result = dict_hasDuplicates( dict );
return result;
}
JNIEXPORT jstring JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getTilesInfo
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jlong dictPtr )
{
jstring result = NULL;
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
#ifdef MEM_DEBUG
MemPoolCtx* mpool = GETMPOOL( globalState );
#endif
DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr;
XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globalState->vtMgr,
NULL, 0, NULL );
dict_writeTilesInfo( dict, stream );
result = streamToJString( env, stream );
stream_destroy( stream, env );
DictionaryCtxt* dict = makeDict( MPPARM(mpool) env, TI_IF(&globalState->ti)
globalState->dictMgr,
globalState->jniutil, jname, jDictBytes, jpath,
NULL, check );
return result;
}
JNIEXPORT jstring JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1tilesToStr
( JNIEnv* env, jclass C, jlong dictPtr, jbyteArray jtiles, jstring jdelim )
{
jstring result = NULL;
DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr;
if ( !!jtiles && !!dict ) {
XP_UCHAR buf[64];
const XP_UCHAR* delim = NULL;
if ( !!jdelim ) {
delim = (*env)->GetStringUTFChars( env, jdelim, NULL );
}
XP_U16 nTiles = (*env)->GetArrayLength( env, jtiles );
jbyte* tiles = (*env)->GetByteArrayElements( env, jtiles, NULL );
XP_U16 strLen = dict_tilesToString( dict, (Tile*)tiles, nTiles,
buf, VSIZE(buf), delim );
if ( 0 < strLen ) {
buf[strLen] = '\0';
result = (*env)->NewStringUTF( env, buf );
}
if ( !!jdelim ) {
(*env)->ReleaseStringUTFChars( env, jdelim, delim );
}
(*env)->ReleaseByteArrayElements( env, jtiles, tiles, 0 );
} else {
XP_LOGFF( "null jtiles array" );
}
return result;
}
JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jlong dictPtr,
jboolean check, jobject jinfo )
{
jboolean result = false;
#ifdef MAP_THREAD_TO_ENV
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
MAP_THREAD( &globalState->ti, env );
#endif
DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr;
if ( NULL != dict ) {
if ( NULL != jinfo ) {
XP_LangCode code = dict_getLangCode( dict );
XP_ASSERT( 0 < code );
setInt( env, jinfo, "langCode", code );
setInt( env, jinfo, "wordCount", dict_getWordCount( dict ) );
setInt( env, jinfo, "wordCount", dict_getWordCount( dict, env ) );
setString( env, jinfo, "md5Sum", dict_getMd5Sum( dict ) );
}
dict_unref( dict, env );
result = true;
}
releaseMPool( globalState );
return result;
}
JNIEXPORT jstring JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getDesc
( JNIEnv* env, jclass C, jlong dictPtr )
{
jstring result = NULL;
DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr;
const XP_UCHAR* disc = dict_getDesc( dict );
if ( NULL != disc && '\0' != disc[0] ) {
result = (*env)->NewStringUTF( env, disc );
}
return result;
}
@ -2509,11 +2631,12 @@ Java_org_eehouse_android_xw4_jni_XwJNI_board_1sendChat
typedef struct _DictIterData {
JNIGlobalState* globalState;
DictionaryCtxt* dict;
DictIter iter;
const DictionaryCtxt* dict;
DictIter* iter;
IndexData idata;
XP_U16 depth;
#ifdef DEBUG
pthread_t lastUser;
XP_U32 guard;
#endif
} DictIterData;
@ -2524,65 +2647,103 @@ static void freeIndices( DictIterData* data );
JNIEXPORT jlong JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_di_1init
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jbyteArray jDictBytes,
jstring jname, jstring jpath )
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jlong dictPtr,
jobjectArray jPatsArr, jint minLen, jint maxLen )
{
jlong closure = 0;
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
MAP_THREAD( &globalState->ti, env );
DictionaryCtxt* dict = makeDict( MPPARM(globalState->mpool) env,
TI_IF(&globalState->ti)
globalState->dictMgr, globalState->jniutil,
jname, jDictBytes, jpath, NULL, false );
DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr;
if ( !!dict ) {
DictIterData* data = XP_CALLOC( globalState->mpool, sizeof(*data) );
data->globalState = globalState;
data->dict = dict;
data->dict = dict_ref( dict, env );
data->depth = 2;
#ifdef DEBUG
data->guard = GI_GUARD;
#endif
jobject jdescs[3];
PatDesc patDescs[3] = {{0}};
jbyteArray jtiles[3];
int len = 0;
if ( !!jPatsArr ) {
len = (*env)->GetArrayLength( env, jPatsArr );
XP_ASSERT( len == 3 );
for ( int ii = 0; ii < len ; ++ii ) {
jdescs[ii] = (*env)->GetObjectArrayElement( env, jPatsArr, ii );
if ( !!jdescs[ii] ) {
if ( getObject( env, jdescs[ii], "tilePat", "[B", &jtiles[ii] ) ) {
patDescs[ii].nTiles = (*env)->GetArrayLength( env, jtiles[ii] );
patDescs[ii].tiles = (Tile*)
(*env)->GetByteArrayElements( env, jtiles[ii], NULL );
patDescs[ii].anyOrderOk = getBool( env, jdescs[ii], "anyOrderOk" );
}
} else {
jtiles[ii] = NULL;
}
}
}
DIMinMax mm = { .min = minLen, .max = maxLen };
data->iter = di_makeIter( data->dict, env, &mm, NULL, 0,
!!jPatsArr ? patDescs : NULL, VSIZE(patDescs) );
for ( int ii = 0; ii < len ; ++ii ) {
if ( !!jtiles[ii] ) {
(*env)->ReleaseByteArrayElements( env, jtiles[ii],
(jbyte*)patDescs[ii].tiles, 0 );
deleteLocalRef( env, jtiles[ii] );
}
}
makeIndex( data );
(void)di_firstWord( data->iter );
closure = (jlong)data;
}
return closure;
}
#define DI_HEADER() { \
#ifdef DEBUG
# define DI_HEADER(THREAD_CHECK) { \
DictIterData* data = (DictIterData*)closure; \
XP_ASSERT( NULL == data || data->guard == GI_GUARD ); \
if ( THREAD_CHECK && !!data ) { \
if ( 0 == data->lastUser ) { \
data->lastUser = pthread_self(); \
} else { \
XP_ASSERT( data->lastUser == pthread_self() ); \
} \
} \
#else
# define DI_HEADER(THREAD_CHECK) { \
DictIterData* data = (DictIterData*)closure; \
#endif
#define DI_HEADER_END() \
}
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_di_1setMinMax
( JNIEnv* env, jclass C, jlong closure, jint min, jint max )
{
DI_HEADER();
if ( NULL != data ) {
di_initIter( &data->iter, data->dict, min, max );
makeIndex( data );
(void)di_firstWord( &data->iter );
}
DI_HEADER_END();
}
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_di_1destroy
( JNIEnv* env, jclass C, jlong closure )
{
DI_HEADER();
DI_HEADER(XP_FALSE);
if ( NULL != data ) {
#ifdef MEM_DEBUG
MemPoolCtx* mpool = data->globalState->mpool;
#endif
dict_unref( data->dict, env );
freeIndices( data );
MAP_REMOVE( &data->globalState->ti, env );
di_freeIter( data->iter, env);
dict_unref( data->dict, env );
XP_FREE( mpool, data );
}
DI_HEADER_END();
@ -2593,31 +2754,25 @@ Java_org_eehouse_android_xw4_jni_XwJNI_di_1wordCount
(JNIEnv* env, jclass C, jlong closure )
{
jint result = 0;
DI_HEADER();
DI_HEADER(XP_TRUE);
if ( NULL != data ) {
result = data->iter.nWords;
result = di_getNWords( data->iter );
}
DI_HEADER_END();
return result;
}
JNIEXPORT jintArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_di_1getCounts
(JNIEnv* env, jclass C, jlong closure )
Java_org_eehouse_android_xw4_jni_XwJNI_di_1getMinMax
( JNIEnv* env, jclass C, jlong closure )
{
jintArray result = NULL;
DI_HEADER();
if ( NULL != data ) {
DictIter iter;
di_initIter( &iter, data->dict, 0, MAX_COLS_DICT );
DI_HEADER(XP_TRUE);
XP_U16 vals[2];
di_getMinMax( data->iter, &vals[0], &vals[1] );
result = makeIntArray( env, VSIZE(vals), vals, sizeof(vals[0]) );
LengthsArray lens;
if ( 0 < di_countWords( &iter, &lens ) ) {
XP_ASSERT( sizeof(jint) == sizeof(lens.lens[0]) );
result = makeIntArray( env, VSIZE(lens.lens), (jint*)&lens.lens,
sizeof(lens.lens[0]) );
}
}
DI_HEADER_END();
return result;
}
@ -2627,7 +2782,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_di_1getPrefixes
( JNIEnv* env, jclass C, jlong closure )
{
jobjectArray result = NULL;
DI_HEADER();
DI_HEADER(XP_TRUE);
if ( NULL != data && NULL != data->idata.prefixes ) {
result = makeStringArray( env, data->idata.count, NULL );
@ -2651,13 +2806,14 @@ Java_org_eehouse_android_xw4_jni_XwJNI_di_1getIndices
( JNIEnv* env, jclass C, jlong closure )
{
jintArray jindices = NULL;
DI_HEADER();
DI_HEADER(XP_TRUE);
if ( NULL != data ) {
XP_ASSERT( !!data->idata.indices );
XP_ASSERT( sizeof(jint) == sizeof(data->idata.indices[0]) );
jindices = makeIntArray( env, data->idata.count,
(jint*)data->idata.indices,
sizeof(data->idata.indices[0]) );
if ( !!data->idata.indices ) { /* filters-block-all case */
XP_ASSERT( sizeof(jint) == sizeof(data->idata.indices[0]) );
jindices = makeIntArray( env, data->idata.count,
(jint*)data->idata.indices,
sizeof(data->idata.indices[0]) );
}
}
DI_HEADER_END();
return jindices;
@ -2668,13 +2824,13 @@ Java_org_eehouse_android_xw4_jni_XwJNI_di_1nthWord
( JNIEnv* env, jclass C, jlong closure, jint nn, jstring jdelim )
{
jstring result = NULL;
DI_HEADER();
DI_HEADER(XP_TRUE);
if ( NULL != data ) {
if ( di_getNthWord( &data->iter, nn, data->depth, &data->idata ) ) {
if ( di_getNthWord( data->iter, env, nn, data->depth, &data->idata ) ) {
XP_UCHAR buf[64];
const XP_UCHAR* delim = NULL == jdelim ? NULL
: (*env)->GetStringUTFChars( env, jdelim, NULL );
di_wordToString( &data->iter, buf, VSIZE(buf), delim );
di_wordToString( data->iter, buf, VSIZE(buf), delim );
result = (*env)->NewStringUTF( env, buf );
if ( !!delim ) {
(*env)->ReleaseStringUTFChars( env, jdelim, delim );
@ -2685,137 +2841,19 @@ Java_org_eehouse_android_xw4_jni_XwJNI_di_1nthWord
return result;
}
typedef struct _FTData {
JNIEnv* env;
jbyteArray arrays[16];
int nArrays;
} FTData;
static XP_Bool
onFoundTiles( void* closure, const Tile* tiles, int nTiles )
{
FTData* ftd = (FTData*)closure;
ftd->arrays[ftd->nArrays++] = makeByteArray( ftd->env, nTiles,
(const jbyte*)tiles );
return ftd->nArrays < VSIZE(ftd->arrays); /* still have room? */
}
JNIEXPORT jobjectArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_di_1strToTiles
( JNIEnv* env, jclass C, jlong closure, jstring jstr )
{
jobjectArray result = NULL;
DI_HEADER();
const char* str = (*env)->GetStringUTFChars( env, jstr, NULL );
FTData ftd = { .env = env, };
dict_tilesForString( data->dict, str, onFoundTiles, &ftd );
if ( ftd.nArrays > 0 ) {
result = makeByteArrayArray( env, ftd.nArrays );
for ( int ii = 0; ii < ftd.nArrays; ++ii ) {
(*env)->SetObjectArrayElement( env, result, ii, ftd.arrays[ii] );
deleteLocalRef( env, ftd.arrays[ii] );
}
}
(*env)->ReleaseStringUTFChars( env, jstr, str );
DI_HEADER_END();
return result;
}
JNIEXPORT jint JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_di_1getStartsWith
( JNIEnv* env, jclass C, jlong closure, jobjectArray jtilesArr )
{
jint result = -1;
DI_HEADER();
int len = (*env)->GetArrayLength( env, jtilesArr );
for ( int ii = 0; ii < len && -1 == result ; ++ii ) {
jbyteArray jtiles = (*env)->GetObjectArrayElement( env, jtilesArr, ii );
XP_U16 nTiles = (*env)->GetArrayLength( env, jtiles );
jbyte* tiles = (*env)->GetByteArrayElements( env, jtiles, NULL );
if ( 0 <= di_findStartsWith( &data->iter, (Tile*)tiles, nTiles ) ) {
result = di_getPosition( &data->iter );
}
(*env)->ReleaseByteArrayElements( env, jtiles, tiles, 0 );
deleteLocalRef( env, jtiles );
}
DI_HEADER_END();
return result;
}
JNIEXPORT jstring JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_di_1tilesToStr
( JNIEnv* env, jclass C, jlong closure, jbyteArray jtiles, jstring jdelim )
{
jstring result = NULL;
DI_HEADER();
XP_UCHAR buf[64];
const XP_UCHAR* delim = NULL;
if ( !!jdelim ) {
delim = (*env)->GetStringUTFChars( env, jdelim, NULL );
}
XP_U16 nTiles = (*env)->GetArrayLength( env, jtiles );
jbyte* tiles = (*env)->GetByteArrayElements( env, jtiles, NULL );
XP_U16 strLen = dict_tilesToString( data->dict, (Tile*)tiles, nTiles,
buf, VSIZE(buf), delim );
if ( 0 < strLen ) {
buf[strLen] = '\0';
result = (*env)->NewStringUTF( env, buf );
}
if ( !!jdelim ) {
(*env)->ReleaseStringUTFChars( env, jdelim, delim );
}
(*env)->ReleaseByteArrayElements( env, jtiles, tiles, 0 );
DI_HEADER_END();
return result;
}
JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_di_1hasDuplicates
( JNIEnv* env, jclass C, jlong closure )
{
jboolean result;
DI_HEADER();
result = dict_hasDuplicates( data->dict );
DI_HEADER_END();
return result;
}
JNIEXPORT jstring JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_di_1getDesc
( JNIEnv* env, jclass C, jlong closure )
{
jstring result = NULL;
DI_HEADER();
if ( NULL != data ) {
const XP_UCHAR* disc = dict_getDesc( data->dict );
if ( NULL != disc && '\0' != disc[0] ) {
result = (*env)->NewStringUTF( env, disc );
}
}
DI_HEADER_END();
return result;
}
static void
freeIndices( DictIterData* data )
{
IndexData* idata = &data->idata;
if ( !!idata->prefixes ) {
XP_FREE( data->globalState->mpool, idata->prefixes );
idata->prefixes = NULL;
}
if( !!idata->indices ) {
XP_FREE( data->globalState->mpool, idata->indices );
idata->indices = NULL;
if ( !!data ) {
IndexData* idata = &data->idata;
if ( !!idata->prefixes ) {
XP_FREE( data->globalState->mpool, idata->prefixes );
idata->prefixes = NULL;
}
if( !!idata->indices ) {
XP_FREE( data->globalState->mpool, idata->indices );
idata->indices = NULL;
}
}
}
@ -2840,7 +2878,7 @@ makeIndex( DictIterData* data )
idata->indices = XP_MALLOC( mpool, count * sizeof(*idata->indices) );
idata->count = count;
di_makeIndex( &data->iter, data->depth, idata );
di_makeIndex( data->iter, data->depth, idata );
if ( 0 < idata->count ) {
idata->prefixes = XP_REALLOC( mpool, idata->prefixes,
idata->count * data->depth *

View file

@ -375,7 +375,7 @@ Introduïu el vostre nom aquí. S\'usarà en crear partides noves. (Podreu
<string name="button_search">Cerca</string>
<string name="word_search_hint">Primeres lletres</string>
<string name="pick_faceup">Tria les fitxes cara amunt</string>
<string name="dict_browse_title_fmt">%1$s (%2$d paraules usant %3$d-%4$d fitxes)</string>
<string name="dict_browse_title_fmt">%1$s (%2$d paraules)</string>
<string name="dict_browse_title1_fmt">%1$s (%2$d paraules usant %3$d fitxes)</string>
<string name="dict_browse_nowords_fmt">No hi ha cap paraula a %1$s que comenci amb %2$s.</string>
<string name="not_again_browse">Aquest botó obre l\'explorador de diccionaris en el diccionari actual del jugador.</string>

View file

@ -375,7 +375,7 @@
<string name="tilepick_all_fmt">%1$d für mich auswählen</string>
<string name="pick_faceup">Spielsteine aufgedeckt auswählen</string>
<string name="disableds_title">Deaktivierte Adresstypen</string>
<string name="dict_browse_title_fmt">%1$s (%2$d Wörter mit %3$d-%4$d Spielsteinen)</string>
<string name="dict_browse_title_fmt">%1$s (%2$d Wörter)</string>
<string name="dict_browse_title1_fmt">%1$s (%2$d Wörter mit %3$d Spielsteinen)</string>
<string name="dict_browse_nowords_fmt">Kein Wort in %1$s beginnt mit %2$s.</string>
<string name="not_again_browse">Dieser Button öffnet den Wortlisten-Browser mit der Wortliste des gegenwärtigen Spielers.</string>

View file

@ -1879,8 +1879,7 @@ ligne, les mots qui viennent d\'être joués.</string>
tiles)</string>-->
<!--<string name="dict_browse_title_fmt">%1$s (%2$d mots utilisant de %3$d à
%4$d jetons)</string>-->
<string name="dict_browse_title_fmt">%1$s (%2$d mots utilisant de
%3$d à %4$d jetons)</string>
<string name="dict_browse_title_fmt">%1$s (%2$d mots)</string>
<!-- -->
<!--<string name="dict_browse_title1_fmt">%1$s (%2$d words using %3$d
tiles)</string>-->

View file

@ -352,7 +352,7 @@
<string name="button_search">検索</string>
<string name="word_search_hint">最初の文字</string>
<string name="pick_faceup">タイル面を選択</string>
<string name="dict_browse_title_fmt">%1$s (%2$d 単語 %3$d-%4$d タイル使用)</string>
<string name="dict_browse_title_fmt">%1$s (%2$d 単語)</string>
<string name="dict_browse_title1_fmt">%1$s (%2$d 単語 %3$d タイル使用)</string>
<string name="dict_browse_nowords_fmt">%1$s に %2$s で開始する単語はありません。</string>
<string name="not_again_browse">このボタンは、現在のプレイヤーの単語リストで、単語リスト ブラウザーを開きます。</string>

View file

@ -223,7 +223,7 @@
<string name="word_search_hint">Første bokstaver</string>
<string name="tilepick_all_fmt">Plukk %1$d for meg</string>
<string name="disableds_title">Avskrudde adressetyper</string>
<string name="dict_browse_title_fmt">%1$s (%2$d ord ved bruk av %3$d-%4$d flis)</string>
<string name="dict_browse_title_fmt">%1$s (%2$d ord)</string>
<string name="dict_browse_title1_fmt">%1$s (%2$d ord ved bruk av %3$d flis</string>
<string name="dict_browse_nowords_fmt">Ingen ord i %1$s starter med %2$s.</string>
<string name="not_again_browse">Denne knappen åpner ordlisteutforskeren i gjeldende spillers ordliste.</string>

View file

@ -354,8 +354,7 @@
<string name="button_search">Vind</string>
<string name="word_search_hint">Eerste letters</string>
<string name="pick_faceup">Pak letters met de letter naar boven</string>
<string name="dict_browse_title_fmt">%1$s (%2$d woorden met %3$d-%4$d letters)</string>
<string name="dict_browse_title1_fmt">%1$s (%2$d woorden met %3$d letters)</string>
<string name="dict_browse_title_fmt">%1$s (%2$d woorden)</string>
<string name="dict_browse_nowords_fmt">Geen woord in %1$s begint met %2$s.</string>
<string name="not_again_browse">Deze knop opent de woordenlijst browser voor de woordenlijst van de speler die aan de beurt is.</string>
<string name="not_again_browseall">Deze knop opent de woordenlijst browser voor de woordenlijst van jouw keuze.</string>

View file

@ -460,7 +460,7 @@
\n(Usunięcie grupy Archiwum jest bezpieczne, ponieważ zostanie ona utworzona na nowo, gdy będzie potrzebna.)</string>
<string name="button_move">Rusz się.</string>
<string name="tilepick_all_fmt">Wybierz %1$d dla mnie</string>
<string name="dict_browse_title_fmt">"%1$s (%2$d słów przy użyciu %3$d-%4$d kafelków)"</string>
<string name="dict_browse_title_fmt">"%1$s (%2$d słów)"</string>
<string name="dict_browse_title1_fmt">%1$s (%2$d wyrazy przy użyciu %3$d kafelków)</string>
<string name="dict_browse_nowords_fmt">Żadne słowo w %1$s nie zaczyna się od %2$s.</string>
<string name="not_again_browse">Ten przycisk otwiera przeglądarkę listy słów na liście słów bieżącego gracza.</string>

View file

@ -1175,12 +1175,6 @@
<string name="dict_browse_nowords_fmt">XLATE ME: No word in %1$s starts with
%2$s.</string>
<!-- -->
<string name="dict_browse_title1_fmt">XLATE ME: %1$s (%2$d words using %3$d
tiles)</string>
<!-- -->
<string name="dict_browse_title_fmt">XLATE ME: %1$s (%2$d words using %3$d-%4$d
tiles)</string>
<!-- -->
<string name="pick_faceup">XLATE ME: Pick tiles face-up</string>
<!-- -->
<!-- -->

View file

@ -300,7 +300,7 @@ board_setDraw( BoardCtxt* board, XWEnv xwe, DrawCtx* draw )
{
board->draw = draw;
if ( !!draw ) {
DictionaryCtxt* langDict = model_getDictionary( board->model );
const DictionaryCtxt* langDict = model_getDictionary( board->model );
draw_dictChanged( draw, xwe, -1, langDict );
}
}
@ -1058,7 +1058,7 @@ boardNotifyTrade( BoardCtxt* board, XWEnv xwe, const TrayTileSet* tiles )
{
const XP_UCHAR* tfaces[MAX_TRAY_TILES];
XP_U16 ii;
DictionaryCtxt* dict = model_getDictionary( board->model );
const DictionaryCtxt* dict = model_getDictionary( board->model );
for ( ii = 0; ii < tiles->nTiles; ++ii ) {
tfaces[ii] = dict_getTileString( dict, tiles->tiles[ii] );
@ -3733,7 +3733,7 @@ typedef struct _FTData {
} FTData;
static XP_Bool
foundTiles( void* closure, const Tile* tiles, int len )
foundTiles( void* closure, const Tile* tiles, int XP_UNUSED_DBG(len) )
{
XP_ASSERT( 1 == len );
FTData* ftp = (FTData*)closure;
@ -3758,12 +3758,12 @@ keyToIndex( BoardCtxt* board, XP_Key key, Tile* blankFace )
# endif
if ( tileIndex < 0 ) {
DictionaryCtxt* dict = model_getDictionary( model );
const DictionaryCtxt* dict = model_getDictionary( model );
XP_UCHAR buf[2] = { key, '\0' };
/* Figure out if we have the tile in the tray */
FTData ftd = {0};
dict_tilesForString( dict, buf, foundTiles, &ftd );
dict_tilesForString( dict, buf, 0, foundTiles, &ftd );
if ( ftd.found ) {
XP_S16 turn = board->selPlayer;
tileIndex = model_trayContains( model, turn, ftd.tile );

View file

@ -377,7 +377,7 @@ drawCell( BoardCtxt* board, XWEnv xwe, const XP_U16 col,
XP_Bool isBlank, isEmpty, pending = XP_FALSE;
XWBonusType bonus;
ModelCtxt* model = board->model;
DictionaryCtxt* dict = model_getDictionary( model );
const DictionaryCtxt* dict = model_getDictionary( model );
XP_U16 modelCol, modelRow;
if ( dict != NULL && getCellRect( board, col, row, &cellRect ) ) {

View file

@ -243,7 +243,7 @@ typedef struct CommonPrefs {
} CommonPrefs;
typedef struct _PlayerDicts {
DictionaryCtxt* dicts[MAX_NUM_PLAYERS];
const DictionaryCtxt* dicts[MAX_NUM_PLAYERS];
} PlayerDicts;
typedef uint64_t MQTTDevID;

File diff suppressed because it is too large Load diff

View file

@ -37,21 +37,6 @@ extern "C" {
/* API for iterating over a dict */
typedef XP_S32 DictPosition;
typedef struct _DictIter {
XP_U16 nEdges;
array_edge* edges[MAX_COLS_DICT];
#ifdef XWFEATURE_WALKDICT_FILTER
XP_U16 min;
XP_U16 max;
#endif
#ifdef DEBUG
XP_U32 guard;
#endif
const DictionaryCtxt* dict;
XP_U32 nWords;
DictPosition position;
} DictIter;
typedef struct _IndexData {
DictPosition* indices;
@ -63,19 +48,68 @@ typedef struct _LengthsArray {
XP_U32 lens[MAX_COLS_DICT+1];
} LengthsArray;
void di_initIter( DictIter* iter, const DictionaryCtxt* dict,
XP_U16 min, XP_U16 max );
typedef struct DictIter DictIter;
/* A pattern is a list of Tiles (that must be contained in the wordlist) plus
* regex meta-characters. Tiles are optionally delimited by '.' (to deal with
* the Hungarian case) Supported are:
*
* tile sets (e.g. [A.B.C]); also [^A.B.C] means NOT these, and [+A.B.C] means
* use at most once [+A.B.C]{3} would mean "use all of them once each"
*
* '?' (meaning anything, same as '.' in most regex languages),
*
* '*' meaning 0 or more of what's before
*
* '+' meaning 1 or more of what's before
*
* '{m[,n]}' meaning between m and n of what's before, so ?{2,15} matches
* everything
*
* '()' also required to express word length: (AB_*CD){2,15} is "an word
* beginning with AB and ending with CD from 2 to 15 letters long.
*
* '^' and '$' matching beginning and end of word (not required, so not yet)
*/
/* di_makeIter: It's either-or: provide the pattern as a reg-ex string, or as
* an array of three tile-strings representing starts-with, contains, and
* ends-with. The first is more powerful but I'm not sure it'll ever be part
* of a shipping UI.*/
typedef struct _PatDesc {
const Tile* tiles;
XP_U16 nTiles;
XP_Bool anyOrderOk;
} PatDesc;
typedef struct _DIMinMax {
XP_U16 min;
XP_U16 max;
} DIMinMax;
DictIter* di_makeIter( const DictionaryCtxt* dict, XWEnv xwe,
const DIMinMax* lens, /* NULL means use defaults */
const XP_UCHAR** strPats, XP_U16 nStrPats,
const PatDesc* pats, XP_U16 nPatDescs );
void di_freeIter( DictIter* iter, XWEnv xwe );
#ifdef XWFEATURE_TESTPATSTR
XP_Bool di_stringMatches( DictIter* iter, const XP_UCHAR* string );
#endif
XP_U32 di_countWords( const DictIter* iter, LengthsArray* lens );
void di_makeIndex( const DictIter* iter, XP_U16 depth, IndexData* data );
XP_Bool di_firstWord( DictIter* iter );
XP_Bool di_lastWord( DictIter* iter );
XP_Bool di_getNextWord( DictIter* iter );
XP_Bool di_getPrevWord( DictIter* iter );
XP_Bool di_getNthWord( DictIter* iter, DictPosition position, XP_U16 depth,
XP_Bool di_getNthWord( DictIter* iter, XWEnv xwe, DictPosition position, XP_U16 depth,
const IndexData* data );
XP_U32 di_getNWords( const DictIter* iter );
void di_getMinMax( const DictIter* iter, XP_U16* min, XP_U16* max );
void di_wordToString( const DictIter* iter, XP_UCHAR* buf, XP_U16 buflen,
const XP_UCHAR* delim );
XP_S16 di_findStartsWith( DictIter* iter, const Tile* prefix, XP_U16 nTiles );
void di_stringToTiles( const XP_UCHAR* str, Tile out[], XP_U16* nTiles );
DictPosition di_getPosition( const DictIter* iter );
#ifdef CPLUS

View file

@ -39,7 +39,7 @@ extern "C" {
typedef struct _DictPair {
XP_UCHAR* key;
DictionaryCtxt* dict;
const DictionaryCtxt* dict;
} DictPair;
struct DictMgrCtxt {
@ -81,10 +81,10 @@ dmgr_destroy( DictMgrCtxt* dmgr, XWEnv xwe )
XP_FREE( dmgr->mpool, dmgr );
}
DictionaryCtxt*
const DictionaryCtxt*
dmgr_get( DictMgrCtxt* dmgr, XWEnv xwe, const XP_UCHAR* key )
{
DictionaryCtxt* result = NULL;
const DictionaryCtxt* result = NULL;
pthread_mutex_lock( &dmgr->mutex );

View file

@ -33,7 +33,7 @@ DictMgrCtxt* dmgr_make( MPFORMAL_NOCOMMA );
void dmgr_destroy( DictMgrCtxt* dmgr, XWEnv xwe );
void dmgr_put( DictMgrCtxt* dmgr, XWEnv xwe, const XP_UCHAR* key, DictionaryCtxt* dict );
DictionaryCtxt* dmgr_get( DictMgrCtxt* dmgr, XWEnv xwe, const XP_UCHAR* key );
const DictionaryCtxt* dmgr_get( DictMgrCtxt* dmgr, XWEnv xwe, const XP_UCHAR* key );
#ifdef CPLUS
}

View file

@ -42,44 +42,46 @@ extern "C" {
static XP_Bool makeBitmap( XP_U8 const** ptrp, const XP_U8* end );
DictionaryCtxt*
p_dict_ref( DictionaryCtxt* dict, XWEnv XP_UNUSED(xwe)
const DictionaryCtxt*
p_dict_ref( const DictionaryCtxt* dict, XWEnv XP_UNUSED(xwe)
#ifdef DEBUG_REF
,const char* func, const char* file, int line
#endif
)
{
if ( !!dict ) {
pthread_mutex_lock( &dict->mutex );
++dict->refCount;
DictionaryCtxt* _dict = (DictionaryCtxt*)dict;
pthread_mutex_lock( &_dict->mutex );
++_dict->refCount;
#ifdef DEBUG_REF
XP_LOGF( "%s(dict=%p): refCount now %d (from line %d of %s() in %s)",
__func__, dict, dict->refCount, line, func, file );
#endif
pthread_mutex_unlock( &dict->mutex );
pthread_mutex_unlock( &_dict->mutex );
}
return dict;
}
void
p_dict_unref( DictionaryCtxt* dict, XWEnv xwe
p_dict_unref( const DictionaryCtxt* dict, XWEnv xwe
#ifdef DEBUG_REF
,const char* func, const char* file, int line
#endif
)
{
if ( !!dict ) {
pthread_mutex_lock( &dict->mutex );
--dict->refCount;
XP_ASSERT( 0 <= dict->refCount );
DictionaryCtxt* _dict = (DictionaryCtxt*)dict;
pthread_mutex_lock( &_dict->mutex );
--_dict->refCount;
XP_ASSERT( 0 <= _dict->refCount );
#ifdef DEBUG_REF
XP_LOGF( "%s(dict=%p): refCount now %d (from line %d of %s() in %s)",
__func__, dict, dict->refCount, line, func, file );
#endif
pthread_mutex_unlock( &dict->mutex );
if ( 0 == dict->refCount ) {
pthread_mutex_destroy( &dict->mutex );
(*dict->destructor)( dict, xwe );
pthread_mutex_unlock( &_dict->mutex );
if ( 0 == _dict->refCount ) {
pthread_mutex_destroy( &_dict->mutex );
(*dict->destructor)( _dict, xwe );
}
}
}
@ -531,12 +533,13 @@ dict_tilesToString( const DictionaryCtxt* dict, const Tile* tiles,
*/
static XP_Bool
tilesForStringImpl( const DictionaryCtxt* dict, const XP_UCHAR* str,
tilesForStringImpl( const DictionaryCtxt* dict,
const XP_UCHAR* str, XP_U16 strLen,
Tile* tiles, XP_U16 nTiles, XP_U16 nFound,
OnFoundTiles proc, void* closure )
{
XP_Bool goOn;
if ( nFound == nTiles || '\0' == str[0] ) {
if ( nFound == nTiles || 0 == strLen ) {
/* We've recursed to the end and have found a tile! */
goOn = (*proc)( closure, tiles, nFound );
} else {
@ -544,20 +547,19 @@ tilesForStringImpl( const DictionaryCtxt* dict, const XP_UCHAR* str,
XP_U16 nFaces = dict_numTileFaces( dict );
for ( Tile tile = 0; goOn && tile < nFaces; ++tile ) {
if ( tile != dict->blankTile ) {
for ( const XP_UCHAR* facep = NULL; ; ) {
facep = dict_getNextTileString( dict, tile, facep );
if ( !facep ) {
break;
}
XP_U16 faceLen = XP_STRLEN( facep );
if ( 0 == XP_STRNCMP( facep, str, faceLen ) ) {
tiles[nFound] = tile;
goOn = tilesForStringImpl( dict, str + faceLen,
tiles, nTiles, nFound + 1,
proc, closure );
break; /* impossible to have than one match per tile */
}
for ( const XP_UCHAR* facep = NULL; ; ) {
facep = dict_getNextTileString( dict, tile, facep );
if ( !facep ) {
break;
}
XP_U16 faceLen = XP_STRLEN( facep );
if ( 0 == XP_STRNCMP( facep, str, faceLen ) ) {
tiles[nFound] = tile;
goOn = tilesForStringImpl( dict, str + faceLen,
strLen - faceLen,
tiles, nTiles, nFound + 1,
proc, closure );
break; /* impossible to have than one match per tile */
}
}
}
@ -567,10 +569,13 @@ tilesForStringImpl( const DictionaryCtxt* dict, const XP_UCHAR* str,
void
dict_tilesForString( const DictionaryCtxt* dict, const XP_UCHAR* str,
OnFoundTiles proc, void* closure )
XP_U16 strLen, OnFoundTiles proc, void* closure )
{
Tile tiles[32];
tilesForStringImpl( dict, str, tiles, VSIZE(tiles), 0, proc, closure );
if ( 0 == strLen ) {
strLen = XP_STRLEN( str );
}
tilesForStringImpl( dict, str, strLen, tiles, VSIZE(tiles), 0, proc, closure );
} /* dict_tilesForString */
XP_Bool
@ -638,6 +643,21 @@ ucharsToNarrow( const DictionaryCtxt* dict, XP_UCHAR* buf, XP_U16* bufsizep )
*bufsizep = nUsed;
}
/* Summarize tile info in a way it can be presented to users */
void
dict_writeTilesInfo( const DictionaryCtxt* dict, XWStreamCtxt* stream )
{
XP_U16 nFaces = dict_numTileFaces( dict );
for ( Tile tile = 0; tile < nFaces; ++tile ) {
XP_U16 val = dict_getTileValue( dict, tile );
XP_U16 count = dict_numTiles( dict, tile );
const XP_UCHAR* face = dict_getTileString( dict, tile );
XP_UCHAR buf[32];
XP_SNPRINTF( buf, VSIZE(buf), "%s\t%d\t%d\n", face, count, val );
stream_catString( stream, buf );
}
}
void
dict_writeToStream( const DictionaryCtxt* dict, XWStreamCtxt* stream )
{
@ -862,14 +882,14 @@ dict_getLangCode( const DictionaryCtxt* dict )
}
XP_U32
dict_getWordCount( const DictionaryCtxt* dict )
dict_getWordCount( const DictionaryCtxt* dict, XWEnv xwe )
{
XP_U32 nWords = dict->nWords;
#ifdef XWFEATURE_WALKDICT
if ( 0 == nWords ) {
DictIter iter;
di_initIter( &iter, dict, 0, MAX_COLS_DICT );
nWords = di_countWords( &iter, NULL );
DictIter* iter = di_makeIter( dict, xwe, NULL, NULL, 0, NULL, 0 );
nWords = di_countWords( iter, NULL );
di_freeIter( iter, xwe );
}
#endif
return nWords;

View file

@ -146,18 +146,18 @@ struct DictionaryCtxt {
((ACCEPTINGMASK_NEW & ((array_edge_old*)(e))->bits) != 0)
#define IS_LAST_EDGE(d,e) \
((LASTEDGEMASK_NEW & ((array_edge_old*)(e))->bits) != 0)
#define EDGETILE(d,edge) \
#define EDGETILE(dict,edge) \
((Tile)(((array_edge_old*)(edge))->bits & \
((d)->is_4_byte?LETTERMASK_NEW_4:LETTERMASK_NEW_3)))
((dict)->is_4_byte?LETTERMASK_NEW_4:LETTERMASK_NEW_3)))
DictionaryCtxt* p_dict_ref( DictionaryCtxt* dict, XWEnv xwe
const DictionaryCtxt* p_dict_ref( const DictionaryCtxt* dict, XWEnv xwe
#ifdef DEBUG_REF
,const char* func, const char* file, int line
,const char* func, const char* file, int line
#endif
);
void p_dict_unref( DictionaryCtxt* dict, XWEnv xwe
void p_dict_unref( const DictionaryCtxt* dict, XWEnv xwe
#ifdef DEBUG_REF
,const char* func, const char* file, int line
,const char* func, const char* file, int line
#endif
);
void dict_unref_all( PlayerDicts* dicts, XWEnv xwe );
@ -191,20 +191,22 @@ const XP_UCHAR* dict_getLangName(const DictionaryCtxt* ctxt );
XP_Bool dict_isUTF8( const DictionaryCtxt* ctxt );
typedef XP_Bool (*OnFoundTiles)(void* closure, const Tile* tiles, int len);
void dict_tilesForString( const DictionaryCtxt* dict, const XP_UCHAR* key,
OnFoundTiles proc, void* closure );
void dict_tilesForString( const DictionaryCtxt* dict, const XP_UCHAR* str,
XP_U16 strLen, OnFoundTiles proc, void* closure );
XP_Bool dict_faceIsBitmap( const DictionaryCtxt* dict, Tile tile );
void dict_getFaceBitmaps( const DictionaryCtxt* dict, Tile tile,
XP_Bitmaps* bmps );
XP_LangCode dict_getLangCode( const DictionaryCtxt* dict );
XP_U32 dict_getWordCount( const DictionaryCtxt* dict );
XP_U32 dict_getWordCount( const DictionaryCtxt* dict, XWEnv xwe );
const XP_UCHAR* dict_getDesc( const DictionaryCtxt* dict );
const XP_UCHAR* dict_getMd5Sum( const DictionaryCtxt* dict );
XP_Bool dict_hasDuplicates( const DictionaryCtxt* dict );
void dict_writeTilesInfo( const DictionaryCtxt* ctxt, XWStreamCtxt* stream );
void dict_writeToStream( const DictionaryCtxt* ctxt, XWStreamCtxt* stream );
void dict_loadFromStream( DictionaryCtxt* dict, XWEnv xwe, XWStreamCtxt* stream );

View file

@ -757,7 +757,7 @@ figureCrosschecks( EngineCtxt* engine, XP_U16 x, XP_U16 y, XP_U16* scoreP,
} /* figureCrosschecks */
XP_Bool
engine_check( DictionaryCtxt* dict, Tile* tiles, XP_U16 nTiles )
engine_check( const DictionaryCtxt* dict, Tile* tiles, XP_U16 nTiles )
{
array_edge* in_edge = dict_getTopEdge( dict );

View file

@ -63,7 +63,7 @@ XP_Bool engine_findMove( EngineCtxt* ctxt, XWEnv xwe, const ModelCtxt* model, XP
#endif
XP_U16 robotIQ, XP_Bool* canMove,
MoveInfo* result, XP_U16* score );
XP_Bool engine_check( DictionaryCtxt* dict, Tile* buf, XP_U16 buflen );
XP_Bool engine_check( const DictionaryCtxt* dict, Tile* buf, XP_U16 buflen );
#ifdef CPLUS
}

View file

@ -51,8 +51,8 @@ static void notifyBoardListeners( ModelCtxt* model, XWEnv xwe, XP_U16 turn,
static void notifyTrayListeners( ModelCtxt* model, XP_U16 turn,
XP_S16 index1, XP_S16 index2 );
static void notifyDictListeners( ModelCtxt* model, XWEnv xwe, XP_S16 playerNum,
DictionaryCtxt* oldDict,
DictionaryCtxt* newDict );
const DictionaryCtxt* oldDict,
const DictionaryCtxt* newDict );
static void model_unrefDicts( ModelCtxt* model, XWEnv xwe );
static CellTile getModelTileRaw( const ModelCtxt* model, XP_U16 col,
@ -91,8 +91,8 @@ static void assertDiffTurn( ModelCtxt* model, XWEnv xwe, XP_U16 turn,
*
****************************************************************************/
ModelCtxt*
model_make( MPFORMAL XWEnv xwe, DictionaryCtxt* dict, const PlayerDicts* dicts,
XW_UtilCtxt* util, XP_U16 nCols )
model_make( MPFORMAL XWEnv xwe, const DictionaryCtxt* dict,
const PlayerDicts* dicts, XW_UtilCtxt* util, XP_U16 nCols )
{
ModelCtxt* result = (ModelCtxt*)XP_MALLOC( mpool, sizeof( *result ) );
if ( result != NULL ) {
@ -118,7 +118,7 @@ model_make( MPFORMAL XWEnv xwe, DictionaryCtxt* dict, const PlayerDicts* dicts,
ModelCtxt*
model_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream,
DictionaryCtxt* dict, const PlayerDicts* dicts,
const DictionaryCtxt* dict, const PlayerDicts* dicts,
XW_UtilCtxt* util )
{
ModelCtxt* model;
@ -574,9 +574,9 @@ setStackBits( ModelCtxt* model, const DictionaryCtxt* dict )
}
void
model_setDictionary( ModelCtxt* model, XWEnv xwe, DictionaryCtxt* dict )
model_setDictionary( ModelCtxt* model, XWEnv xwe, const DictionaryCtxt* dict )
{
DictionaryCtxt* oldDict = model->vol.dict;
const DictionaryCtxt* oldDict = model->vol.dict;
model->vol.dict = dict_ref( dict, xwe );
if ( !!dict ) {
@ -593,11 +593,11 @@ model_setPlayerDicts( ModelCtxt* model, XWEnv xwe, const PlayerDicts* dicts )
if ( !!dicts ) {
XP_U16 ii;
#ifdef DEBUG
DictionaryCtxt* gameDict = model_getDictionary( model );
const DictionaryCtxt* gameDict = model_getDictionary( model );
#endif
for ( ii = 0; ii < VSIZE(dicts->dicts); ++ii ) {
DictionaryCtxt* oldDict = model->vol.dicts.dicts[ii];
DictionaryCtxt* newDict = dicts->dicts[ii];
const DictionaryCtxt* oldDict = model->vol.dicts.dicts[ii];
const DictionaryCtxt* newDict = dicts->dicts[ii];
if ( oldDict != newDict ) {
XP_ASSERT( NULL == newDict || NULL == gameDict
|| dict_tilesAreSame( gameDict, newDict ) );
@ -612,21 +612,21 @@ model_setPlayerDicts( ModelCtxt* model, XWEnv xwe, const PlayerDicts* dicts )
}
}
DictionaryCtxt*
const DictionaryCtxt*
model_getDictionary( const ModelCtxt* model )
{
XP_U16 ii;
DictionaryCtxt* result = model->vol.dict;
const DictionaryCtxt* result = model->vol.dict;
for ( ii = 0; !result && ii < VSIZE(model->vol.dicts.dicts); ++ii ) {
result = model->vol.dicts.dicts[ii];
}
return result;
} /* model_getDictionary */
DictionaryCtxt*
const DictionaryCtxt*
model_getPlayerDict( const ModelCtxt* model, XP_S16 playerNum )
{
DictionaryCtxt* dict = NULL;
const DictionaryCtxt* dict = NULL;
if ( 0 <= playerNum && playerNum < VSIZE(model->vol.dicts.dicts) ) {
dict = model->vol.dicts.dicts[playerNum];
}
@ -1474,7 +1474,7 @@ model_packTilesUtil( ModelCtxt* model, PoolContext* pool,
XP_U16* nUsed, const XP_UCHAR** texts,
Tile* tiles )
{
DictionaryCtxt* dict = model_getDictionary(model);
const DictionaryCtxt* dict = model_getDictionary(model);
XP_U16 nFaces = dict_numTileFaces( dict );
Tile blankFace = dict_getBlankTile( dict );
Tile tile;
@ -2219,7 +2219,7 @@ notifyTrayListeners( ModelCtxt* model, XP_U16 turn, XP_S16 index1,
static void
notifyDictListeners( ModelCtxt* model, XWEnv xwe, XP_S16 playerNum,
DictionaryCtxt* oldDict, DictionaryCtxt* newDict )
const DictionaryCtxt* oldDict, const DictionaryCtxt* newDict )
{
if ( model->vol.dictListenerFunc != NULL ) {
(*model->vol.dictListenerFunc)( model->vol.dictListenerData, xwe,
@ -2234,8 +2234,8 @@ printString( XWStreamCtxt* stream, const XP_UCHAR* str )
} /* printString */
static XP_UCHAR*
formatTray( const TrayTileSet* tiles, DictionaryCtxt* dict, XP_UCHAR* buf,
XP_U16 bufSize, XP_Bool keepHidden )
formatTray( const TrayTileSet* tiles, const DictionaryCtxt* dict,
XP_UCHAR* buf, XP_U16 bufSize, XP_Bool keepHidden )
{
if ( keepHidden ) {
XP_U16 ii;
@ -2253,7 +2253,7 @@ formatTray( const TrayTileSet* tiles, DictionaryCtxt* dict, XP_UCHAR* buf,
typedef struct MovePrintClosure {
XWStreamCtxt* stream;
DictionaryCtxt* dict;
const DictionaryCtxt* dict;
XP_U16 nPrinted;
XP_Bool keepHidden;
XP_U32 lastPauseWhen;
@ -2339,7 +2339,7 @@ printMovePost( ModelCtxt* model, XWEnv xwe, XP_U16 XP_UNUSED(moveN),
if ( entry->moveType != ASSIGN_TYPE ) {
MovePrintClosure* closure = (MovePrintClosure*)p_closure;
XWStreamCtxt* stream = closure->stream;
DictionaryCtxt* dict = closure->dict;
const DictionaryCtxt* dict = closure->dict;
const XP_UCHAR* format;
XP_U16 nTiles;

View file

@ -74,11 +74,11 @@ typedef XP_U8 TileBit; /* bits indicating selection of tiles in tray */
only */
ModelCtxt* model_make( MPFORMAL XWEnv xwe, DictionaryCtxt* dict,
ModelCtxt* model_make( MPFORMAL XWEnv xwe, const DictionaryCtxt* dict,
const PlayerDicts* dicts, XW_UtilCtxt* util, XP_U16 nCols );
ModelCtxt* model_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream,
DictionaryCtxt* dict, const PlayerDicts* dicts,
const DictionaryCtxt* dict, const PlayerDicts* dicts,
XW_UtilCtxt* util );
void model_writeToStream( const ModelCtxt* model, XWStreamCtxt* stream );
@ -97,11 +97,11 @@ XP_Bool model_popToHash( ModelCtxt* model, XWEnv xwe, const XP_U32 hash,
void model_setNPlayers( ModelCtxt* model, XP_U16 numPlayers );
XP_U16 model_getNPlayers( const ModelCtxt* model );
void model_setDictionary( ModelCtxt* model, XWEnv xwe, DictionaryCtxt* dict );
DictionaryCtxt* model_getDictionary( const ModelCtxt* model );
void model_setDictionary( ModelCtxt* model, XWEnv xwe, const DictionaryCtxt* dict );
const DictionaryCtxt* model_getDictionary( const ModelCtxt* model );
void model_setPlayerDicts( ModelCtxt* model, XWEnv xwe, const PlayerDicts* dicts );
DictionaryCtxt* model_getPlayerDict( const ModelCtxt* model, XP_S16 playerNum );
const DictionaryCtxt* model_getPlayerDict( const ModelCtxt* model, XP_S16 playerNum );
XP_Bool model_getTile( const ModelCtxt* model, XP_U16 col, XP_U16 row,
XP_Bool getPending, XP_S16 turn,

View file

@ -54,7 +54,7 @@ typedef struct ModelVolatiles {
XW_DUtilCtxt* dutil;
XW_UtilCtxt* util;
struct CurGameInfo* gi;
DictionaryCtxt* dict;
const DictionaryCtxt* dict;
PlayerDicts dicts;
StackCtxt* stack;
BoardListener boardListenerFunc;

View file

@ -52,7 +52,7 @@ static XP_U16 scoreWord( const ModelCtxt* model, XWEnv xwe, XP_U16 turn,
as little impact as possible on the speed when the robot's looking for FAST
scoring */
typedef struct WordScoreFormatter {
DictionaryCtxt* dict;
const DictionaryCtxt* dict;
XP_UCHAR fullBuf[80];
XP_UCHAR wordBuf[MAX_ROWS+1];
@ -61,7 +61,7 @@ typedef struct WordScoreFormatter {
XP_Bool firstPass;
} WordScoreFormatter;
static void wordScoreFormatterInit( WordScoreFormatter* fmtr,
DictionaryCtxt* dict );
const DictionaryCtxt* dict );
static void wordScoreFormatterAddTile( WordScoreFormatter* fmtr, Tile tile,
XP_U16 tileMultiplier,
XP_Bool isBlank );
@ -166,7 +166,7 @@ model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP,
XP_S16 firstDoneIndex = -1; /* not set unless FIRST_DONE_BONUS is set */
const TrayTileSet* tray;
PlayerCtxt* player;
DictionaryCtxt* dict = model_getDictionary( model );
const DictionaryCtxt* dict = model_getDictionary( model );
CurGameInfo* gi = model->vol.gi;
if ( !!finalScoresP ) {
@ -298,7 +298,7 @@ checkScoreMove( ModelCtxt* model, XWEnv xwe, XP_S16 turn, EngineCtxt* engine,
if ( checkDict && 0 < bcs.nBadWords ) {
if ( !silent ) {
XP_ASSERT( !!bcs.stream );
DictionaryCtxt* dict = model_getPlayerDict( model, turn );
const DictionaryCtxt* dict = model_getPlayerDict( model, turn );
util_informWordsBlocked( model->vol.util, xwe, bcs.nBadWords,
bcs.stream, dict_getName( dict ) );
stream_destroy( bcs.stream, xwe );
@ -647,7 +647,7 @@ scoreWord( const ModelCtxt* model, XWEnv xwe, XP_U16 turn,
XP_U16 col, row;
const MoveInfoTile* tiles = movei->tiles;
XP_U16 firstCoord = tiles->varCoord;
DictionaryCtxt* dict = model_getPlayerDict( model, turn );
const DictionaryCtxt* dict = model_getPlayerDict( model, turn );
assertSorted( movei );
@ -831,7 +831,7 @@ find_end( const ModelCtxt* model, XP_U16 col, XP_U16 row,
} /* find_end */
static void
wordScoreFormatterInit( WordScoreFormatter* fmtr, DictionaryCtxt* dict )
wordScoreFormatterInit( WordScoreFormatter* fmtr, const DictionaryCtxt* dict )
{
XP_MEMSET( fmtr, 0, sizeof(*fmtr) );

View file

@ -242,7 +242,7 @@ pool_getNTilesLeftFor( const PoolContext* pool, Tile tile )
} /* pool_remainingTileCount */
void
pool_initFromDict( PoolContext* pool, DictionaryCtxt* dict )
pool_initFromDict( PoolContext* pool, const DictionaryCtxt* dict )
{
const XP_U16 numFaces = dict_numTileFaces( dict );

View file

@ -39,7 +39,7 @@ XP_U16 pool_getNTilesLeftFor( const PoolContext* pool, Tile tile );
PoolContext* pool_make( MPFORMAL_NOCOMMA );
void pool_destroy( PoolContext* pool );
void pool_initFromDict( PoolContext* pool, DictionaryCtxt* dict );
void pool_initFromDict( PoolContext* pool, const DictionaryCtxt* dict );
void pool_writeToStream( PoolContext* pool, XWStreamCtxt* stream );
PoolContext* pool_makeFromStream( MPFORMAL XWStreamCtxt* stream );

View file

@ -895,7 +895,7 @@ static XP_U16
bitsPerTile( ServerCtxt* server )
{
if ( 0 == server->vol.bitsPerTile ) {
DictionaryCtxt* dict = model_getDictionary( server->vol.model );
const DictionaryCtxt* dict = model_getDictionary( server->vol.model );
XP_U16 nFaces = dict_numTileFaces( dict );
server->vol.bitsPerTile = nFaces <= 32? 5 : 6;
}
@ -1537,7 +1537,7 @@ informNeedPickTiles( ServerCtxt* server, XWEnv xwe, XP_Bool initial,
XP_U16 turn, XP_U16 nToPick )
{
ModelCtxt* model = server->vol.model;
DictionaryCtxt* dict = model_getDictionary(model);
const DictionaryCtxt* dict = model_getDictionary(model);
XP_U16 nFaces = dict_numTileFaces( dict );
XP_U16 counts[MAX_UNIQUE_TILES];
const XP_UCHAR* faces[MAX_UNIQUE_TILES];
@ -1864,7 +1864,7 @@ client_readInitialMessage( ServerCtxt* server, XWEnv xwe, XWStreamCtxt* stream )
gi->nPlayers = nPlayers;
model_setNPlayers( model, nPlayers );
DictionaryCtxt* curDict = model_getDictionary( model );
const DictionaryCtxt* curDict = model_getDictionary( model );
XP_ASSERT( !!newDict );
@ -1997,7 +1997,7 @@ sendInitialMessage( ServerCtxt* server, XWEnv xwe )
makeSendableGICopy( server, &localGI, deviceIndex );
gi_writeToStream( stream, &localGI );
DictionaryCtxt* dict = model_getDictionary( model );
const DictionaryCtxt* dict = model_getDictionary( model );
dict_writeToStream( dict, stream );
#ifdef STREAM_VERS_BIGBOARD
if ( STREAM_VERS_DICTNAME <= streamVersion ) {
@ -2202,7 +2202,7 @@ makeNotAVowel( ServerCtxt* server, Tile* newTile )
Tile tile = *newTile;
PoolContext* pool = server->pool;
TrayTileSet set;
DictionaryCtxt* dict = model_getDictionary( server->vol.model );
const DictionaryCtxt* dict = model_getDictionary( server->vol.model );
XP_U8 numGot = 1;
set.nTiles = 1;
@ -2240,7 +2240,7 @@ curTrayAsTexts( ServerCtxt* server, XP_U16 turn, const TrayTileSet* notInTray,
XP_U16* nUsedP, const XP_UCHAR** curTrayText )
{
const TrayTileSet* tileSet = model_getPlayerTiles( server->vol.model, turn );
DictionaryCtxt* dict = model_getDictionary( server->vol.model );
const DictionaryCtxt* dict = model_getDictionary( server->vol.model );
XP_U16 ii, jj;
XP_U16 size = tileSet->nTiles;
const Tile* tp = tileSet->tiles;
@ -2354,7 +2354,7 @@ fetchTiles( ServerCtxt* server, XWEnv xwe, XP_U16 playerNum, XP_U16 nToFetch,
PoolContext* pool = server->pool;
const XP_UCHAR* curTray[MAX_TRAY_TILES];
#ifdef FEATURE_TRAY_EDIT
DictionaryCtxt* dict = model_getDictionary( server->vol.model );
const DictionaryCtxt* dict = model_getDictionary( server->vol.model );
#endif
XP_ASSERT( !!pool );
@ -4184,7 +4184,7 @@ void
server_formatDictCounts( ServerCtxt* server, XWEnv xwe, XWStreamCtxt* stream,
XP_U16 nCols, XP_Bool allFaces )
{
DictionaryCtxt* dict;
const DictionaryCtxt* dict;
Tile tile;
XP_U16 nChars, nPrinted;
XP_UCHAR buf[48];
@ -4249,7 +4249,7 @@ server_formatRemainingTiles( ServerCtxt* server, XWEnv xwe, XWStreamCtxt* stream
PoolContext* pool = server->pool;
if ( !!pool ) {
XP_UCHAR buf[128];
DictionaryCtxt* dict = model_getDictionary( server->vol.model );
const DictionaryCtxt* dict = model_getDictionary( server->vol.model );
Tile tile;
XP_U16 nChars = dict_numTileFaces( dict );
XP_U16 offset;

View file

@ -142,7 +142,7 @@ drawTray( BoardCtxt* board, XWEnv xwe )
if ( draw_trayBegin( board->draw, xwe, &board->trayBounds, turn,
turnScore, dfsFor( board, OBJ_TRAY ) ) ) {
DictionaryCtxt* dictionary = model_getDictionary( board->model );
const DictionaryCtxt* dictionary = model_getDictionary( board->model );
XP_U16 trayInvalBits = board->trayInvalBits;
XP_S16 cursorBits = 0;
XP_Bool cursorOnDivider = XP_FALSE;
@ -278,7 +278,7 @@ getTileDrawInfo( const BoardCtxt* board, Tile tile, XP_Bool isBlank,
XP_Bitmaps* bitmaps, XP_S16* value )
{
const XP_UCHAR* face = NULL;
DictionaryCtxt* dict = model_getDictionary( board->model );
const DictionaryCtxt* dict = model_getDictionary( board->model );
if ( isBlank ) {
tile = dict_getBlankTile( dict );
} else {

View file

@ -44,7 +44,7 @@ LANG_SPECIAL_INFO = \
include ../Makefile.langcommon
SOURCEDICT ?= $(XWDICTPATH)/Catalan/DISC2-LP.dict
SOURCEDICT ?= $(XWDICTPATH)/Catalan/DISC2-LP.txt
$(XWLANG)Main.dict.gz: $(SOURCEDICT) $(MAKEFILE)
cat $< \

View file

@ -194,12 +194,13 @@ byodbins: table.bin charcount.bin values.bin frankspecials.bin info.txt
else
ifeq ($(TARGET_TYPE),WINCE)
byodbins: table.bin charcount.bin values.bin frankspecials.bin info.txt
### WINCE section here ###
all: $(XWLANG)2to8.xwd $(XWLANG)2to9.xwd $(XWLANG)2to15.xwd
../mkxwdcab.pl -f $<
empty: $(XWLANG)0to0.xwd
byodbins: table.bin charcount.bin values.bin frankspecials.bin info.txt
else
(Need to define TARGET_TYPE if get error pointing to this line)
endif #ifeq ($(TARGET_TYPE),FRANK)

View file

@ -110,6 +110,7 @@ DEFINES += -DDISABLE_TILE_SEL
# DEFINES += -DSET_GAMESEED
DEFINES += -DTEXT_MODEL
DEFINES += -DXWFEATURE_WALKDICT
DEFINES += -DXWFEATURE_TESTPATSTR
DEFINES += -DXWFEATURE_WALKDICT_FILTER
DEFINES += -DXWFEATURE_DICTSANITY
DEFINES += -DHASH_STREAM

View file

@ -826,6 +826,11 @@ typedef enum {
,CMD_TESTDICT
,CMD_TESTPRFX
,CMD_TESTMINMAX
#endif
,CMD_DELIM
#ifdef XWFEATURE_TESTPATSTR
,CMD_TESTPAT
,CMD_TESTSTR
#endif
,CMD_DICTDIR
,CMD_PLAYERDICT
@ -959,6 +964,12 @@ static CmdInfoRec CmdInfoRecs[] = {
,{ CMD_TESTDICT, true, "test-dict", "dictionary to be used for iterator test" }
,{ CMD_TESTPRFX, true, "test-prefix", "list first word starting with this" }
,{ CMD_TESTMINMAX, true, "test-minmax", "M:M -- include only words whose len in range" }
#endif
,{ CMD_DELIM, true, "test-delim", "string (should be one char) printed between tile faces" }
#ifdef XWFEATURE_TESTPATSTR
,{ CMD_TESTPAT, true, "test-pat", "pattern: e.g. 'ABC' or 'A[+BC]_{2,5}' (can repeat)" }
,{ CMD_TESTSTR, true, "test-string",
"string to be tested against test-pat; exit with non-0 if doesn't match" }
#endif
,{ CMD_DICTDIR, true, "dict-dir", "path to dir in which dicts will be sought" }
,{ CMD_PLAYERDICT, true, "player-dict", "dictionary name for player (in sequence)" }
@ -1914,6 +1925,8 @@ parsePair( const char* optarg, XP_U16* min, XP_U16* max )
*min = intmin;
*max = intmax;
success = true;
} else {
XP_LOGFF( "bad len params: %d <= %d expected", intmin, intmax );
}
}
}
@ -1929,43 +1942,81 @@ tmp_noop_sigintterm( int XP_UNUSED(sig) )
}
#ifdef XWFEATURE_WALKDICT
//# define PRINT_ALL
static void
testGetNthWord( const DictionaryCtxt* dict, char** XP_UNUSED_DBG(words),
XP_U16 depth, IndexData* data, XP_U16 min, XP_U16 max )
static DictIter*
patsParamsToIter( const LaunchParams* params, const DictionaryCtxt* dict )
{
XP_UCHAR buf[64];
XP_U32 ii, jj;
DictIter iter;
const XP_UCHAR** strPats = NULL;
const XP_UCHAR* _strPats[4];
XP_U16 nStrPats = 0;
const PatDesc* pats = NULL;
XP_U16 nPatDescs = 0;
di_initIter( &iter, dict, min, max );
XP_U32 half = di_countWords( &iter, NULL ) / 2;
if ( !!params->iterTestPats ) {
nStrPats = g_slist_length( params->iterTestPats );
strPats = &_strPats[0];
GSList* iter;
int ii;
for ( ii = 0, iter = params->iterTestPats;
!!iter && ii < nStrPats;
++ii, iter = iter->next ) {
strPats[ii] = iter->data;
}
} else if ( !!params->patStartW || !!params->patContains || !!params->patEndsW ) {
XP_ASSERT(0);
/* and what about the boolean? */
}
DIMinMax dimm;
DIMinMax* dimmp = NULL;
if ( !!params->testMinMax && parsePair( params->testMinMax, &dimm.min, &dimm.max ) ) {
dimmp = &dimm;
}
DictIter* iter = di_makeIter( dict, NULL_XWE, dimmp, strPats, nStrPats,
pats, nPatDescs );
return iter;
}
# define PRINT_ALL
static void
testGetNthWord( const LaunchParams* params, const DictionaryCtxt* dict,
char** XP_UNUSED_DBG(words), XP_U16 depth,
const IndexData* data )
{
DictIter* iter = patsParamsToIter( params, dict );
XP_U32 half = di_countWords( iter, NULL ) / 2;
XP_U32 interval = half / 100;
const XP_UCHAR* delim = params->dumpDelim; /* NULL is ok */
if ( interval == 0 ) {
++interval;
}
XP_UCHAR buf[64];
int ii, jj;
for ( ii = 0, jj = half; ii < half; ii += interval, jj += interval ) {
if ( di_getNthWord( &iter, ii, depth, data ) ) {
di_wordToString( &iter, buf, VSIZE(buf), "." );
if ( di_getNthWord( iter, NULL_XWE, ii, depth, data ) ) {
XP_UCHAR buf[64];
di_wordToString( iter, buf, VSIZE(buf), delim );
XP_ASSERT( 0 == strcmp( buf, words[ii] ) );
# ifdef PRINT_ALL
XP_LOGF( "%s: word[%ld]: %s", __func__, ii, buf );
XP_LOGFF( "word[%d]: %s", ii, buf );
# endif
} else {
XP_ASSERT( 0 );
}
if ( di_getNthWord( &iter, jj, depth, data ) ) {
di_wordToString( &iter, buf, VSIZE(buf), "." );
if ( di_getNthWord( iter, NULL_XWE, jj, depth, data ) ) {
di_wordToString( iter, buf, VSIZE(buf), delim );
XP_ASSERT( 0 == strcmp( buf, words[jj] ) );
# ifdef PRINT_ALL
XP_LOGF( "%s: word[%ld]: %s", __func__, jj, buf );
XP_LOGFF( "word[%d]: %s", jj, buf );
# endif
} else {
XP_ASSERT( 0 );
}
}
di_freeIter( iter, NULL_XWE );
}
typedef struct _FTData {
DictIter* iter;
IndexData* data;
@ -1975,72 +2026,71 @@ typedef struct _FTData {
} FTData;
static XP_Bool
onFoundTiles( void* closure, const Tile* tiles, int nTiles )
onFoundTiles( void* XP_UNUSED(closure), const Tile* XP_UNUSED(tiles),
int XP_UNUSED(nTiles) )
{
FTData* ftp = (FTData*)closure;
XP_S16 lenMatched = di_findStartsWith( ftp->iter, tiles, nTiles );
if ( 0 <= lenMatched ) {
XP_UCHAR buf[32];
XP_UCHAR bufPrev[32] = {0};
di_wordToString( ftp->iter, buf, VSIZE(buf), "." );
XP_LOGFF( "Not doing anything as di_findStartsWith is gone" );
/* FTData* ftp = (FTData*)closure; */
/* XP_S16 lenMatched = di_findStartsWith( ftp->iter, tiles, nTiles ); */
/* if ( 0 <= lenMatched ) { */
/* XP_UCHAR buf[32]; */
/* XP_UCHAR bufPrev[32] = {0}; */
/* di_wordToString( ftp->iter, buf, VSIZE(buf), "." ); */
/* This doesn't work with synonyms like "L-L" for "L·L" */
// XP_ASSERT( 0 == strncasecmp( buf, prefix, lenMatched ) );
/* /\* This doesn't work with synonyms like "L-L" for "L·L" *\/ */
/* // XP_ASSERT( 0 == strncasecmp( buf, prefix, lenMatched ) ); */
DictPosition pos = di_getPosition( ftp->iter );
XP_ASSERT( 0 == strcmp( buf, ftp->words[pos] ) );
if ( pos > 0 ) {
if ( !di_getNthWord( ftp->iter, pos-1, ftp->depth, ftp->data ) ) {
XP_ASSERT( 0 );
}
di_wordToString( ftp->iter, bufPrev, VSIZE(bufPrev), "." );
XP_ASSERT( 0 == strcmp( bufPrev, ftp->words[pos-1] ) );
}
XP_LOGF( "di_getStartsWith(%s) => %s (prev=%s)",
ftp->prefix, buf, bufPrev );
} else {
XP_LOGFF( "nothing starts with %s", ftp->prefix );
}
/* DictPosition pos = di_getPosition( ftp->iter ); */
/* XP_ASSERT( 0 == strcmp( buf, ftp->words[pos] ) ); */
/* if ( pos > 0 ) { */
/* if ( !di_getNthWord( ftp->iter, pos-1, ftp->depth, ftp->data ) ) { */
/* XP_ASSERT( 0 ); */
/* } */
/* di_wordToString( ftp->iter, bufPrev, VSIZE(bufPrev), "." ); */
/* XP_ASSERT( 0 == strcmp( bufPrev, ftp->words[pos-1] ) ); */
/* } */
/* XP_LOGF( "di_getStartsWith(%s) => %s (prev=%s)", */
/* ftp->prefix, buf, bufPrev ); */
/* } else { */
/* XP_LOGFF( "nothing starts with %s", ftp->prefix ); */
/* } */
return XP_TRUE;
}
/** walk_dict_test()
*
* This is just to test that the dict-iterating code works. The words are
* meant to be printed e.g. in a scrolling dialog on Android.
*/
static void
walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
GSList* testPrefixes, const char* testMinMax )
walk_dict_test( MPFORMAL const LaunchParams* params, const DictionaryCtxt* dict,
GSList* testPrefixes )
{
/* This is just to test that the dict-iterating code works. The words are
meant to be printed e.g. in a scrolling dialog on Android. */
DictIter iter;
long jj;
XP_Bool gotOne;
XP_U16 min, max;
if ( !testMinMax || !parsePair( testMinMax, &min, &max ) ) {
min = 0;
max = MAX_COLS_DICT;
}
di_initIter( &iter, dict, min, max );
DictIter* iter = patsParamsToIter( params, dict );
LengthsArray lens;
XP_U32 count = di_countWords( &iter, &lens );
XP_U32 count = di_countWords( iter, &lens );
XP_U32 sum = 0;
for ( jj = 0; jj < VSIZE(lens.lens); ++jj ) {
sum += lens.lens[jj];
XP_LOGF( "%d words of length %ld", lens.lens[jj], jj );
for ( long ii = 0; ii < VSIZE(lens.lens); ++ii ) {
XP_LOGF( "%d words of length %ld", lens.lens[ii], ii );
sum += lens.lens[ii];
}
XP_ASSERT( sum == count );
if ( count > 0 ) {
const XP_UCHAR* delim = params->dumpDelim;
XP_Bool gotOne;
long jj;
char** words = g_malloc( count * sizeof(char*) );
XP_ASSERT( !!words );
for ( jj = 0, gotOne = di_firstWord( &iter );
for ( jj = 0, gotOne = di_firstWord( iter );
gotOne;
gotOne = di_getNextWord( &iter ) ) {
XP_ASSERT( di_getPosition( &iter ) == jj );
gotOne = di_getNextWord( iter ) ) {
XP_ASSERT( di_getPosition( iter ) == jj );
XP_UCHAR buf[64];
di_wordToString( &iter, buf, VSIZE(buf), "." );
di_wordToString( iter, buf, VSIZE(buf), delim );
# ifdef PRINT_ALL
fprintf( stderr, "%.6ld: %s\n", jj, buf );
# endif
@ -2051,12 +2101,13 @@ walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
}
XP_ASSERT( count == jj );
for ( jj = 0, gotOne = di_lastWord( &iter );
XP_LOGFF( "comparing runs in both directions" );
for ( jj = 0, gotOne = di_lastWord( iter );
gotOne;
++jj, gotOne = di_getPrevWord( &iter ) ) {
XP_ASSERT( di_getPosition(&iter) == count-jj-1 );
++jj, gotOne = di_getPrevWord( iter ) ) {
XP_ASSERT( di_getPosition(iter) == count-jj-1 );
XP_UCHAR buf[64];
di_wordToString( &iter, buf, VSIZE(buf), "." );
di_wordToString( iter, buf, VSIZE(buf), delim );
# ifdef PRINT_ALL
fprintf( stderr, "%.6ld: %s\n", jj, buf );
# endif
@ -2069,24 +2120,26 @@ walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
}
}
XP_ASSERT( count == jj );
XP_LOGF( "finished comparing runs in both directions" );
XP_LOGFF( "FINISHED comparing runs in both directions" );
XP_LOGF( "testing getNth" );
testGetNthWord( dict, words, 0, NULL, min, max );
XP_LOGFF( "testing getNth" );
testGetNthWord( params, dict, words, 0, NULL );
XP_LOGFF( "FINISHED testing getNth" );
XP_U16 depth = 2;
XP_U16 maxCount = dict_numTileFaces( dict );
IndexData data;
data.count = maxCount * maxCount;
data.count = maxCount * maxCount; /* squared because depth == 2! */
data.indices = XP_MALLOC( mpool,
data.count * depth * sizeof(data.indices[0]) );
data.prefixes = XP_MALLOC( mpool,
depth * data.count * sizeof(data.prefixes[0]) );
XP_LOGF( "making index..." );
di_makeIndex( &iter, depth, &data );
XP_LOGF( "DONE making index" );
di_makeIndex( iter, depth, &data );
XP_LOGF( "DONE making index (have %d indices)", data.count );
/* Resize 'em in case not all slots filled */
data.indices = XP_REALLOC( mpool, data.indices,
data.count * depth * sizeof(*data.indices) );
data.prefixes = XP_REALLOC( mpool, data.prefixes,
@ -2098,10 +2151,10 @@ walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
}
XP_ASSERT( word.index == indices[ii] );
XP_UCHAR buf1[64];
dict_wordToString( dict, &word, buf1, VSIZE(buf1), "." );
dict_wordToString( dict, &word, buf1, VSIZE(buf1), delim );
XP_UCHAR buf2[64] = {0};
if ( ii > 0 && dict_getNthWord( dict, &word, indices[ii]-1 ) ) {
dict_wordToString( dict, &word, buf2, VSIZE(buf2), "." );
dict_wordToString( dict, &word, buf2, VSIZE(buf2), delim );
}
char prfx[8];
dict_tilesToString( dict, &prefixes[depth*ii], depth, prfx,
@ -2112,7 +2165,8 @@ walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
#endif
XP_LOGFF( "testing getNth WITH INDEXING" );
testGetNthWord( dict, words, depth, &data, min, max );
testGetNthWord( params, dict, words, depth, &data );
XP_LOGFF( "DONE testing getNth WITH INDEXING" );
if ( !!testPrefixes ) {
int ii;
@ -2121,21 +2175,22 @@ walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
gchar* prefix = (gchar*)g_slist_nth_data( testPrefixes, ii );
XP_LOGFF( "prefix %d: %s", ii, prefix );
FTData foundTilesData = { .iter = &iter, .words = words,
FTData foundTilesData = { .iter = iter, .words = words,
.depth = depth, .data = &data,
.prefix = prefix, };
dict_tilesForString( dict, prefix, onFoundTiles, &foundTilesData );
dict_tilesForString( dict, prefix, 0, onFoundTiles, &foundTilesData );
}
}
XP_FREE( mpool, data.indices );
XP_FREE( mpool, data.prefixes );
}
di_freeIter( iter, NULL_XWE );
XP_LOGF( "done" );
}
static void
walk_dict_test_all( MPFORMAL const LaunchParams* params, GSList* testDicts,
GSList* testPrefixes, const char* testMinMax )
GSList* testPrefixes )
{
int ii;
guint count = g_slist_length( testDicts );
@ -2146,7 +2201,7 @@ walk_dict_test_all( MPFORMAL const LaunchParams* params, GSList* testDicts,
params->useMmap );
if ( NULL != dict ) {
XP_LOGF( "walk_dict_test(%s)", name );
walk_dict_test( MPPARM(mpool) dict, testPrefixes, testMinMax );
walk_dict_test( MPPARM(mpool) params, dict, testPrefixes );
dict_unref( dict, NULL_XWE );
}
}
@ -2154,17 +2209,18 @@ walk_dict_test_all( MPFORMAL const LaunchParams* params, GSList* testDicts,
#endif
static void
dumpDict( DictionaryCtxt* dict )
dumpDict( const LaunchParams* params, DictionaryCtxt* dict )
{
DictIter iter;
di_initIter( &iter, dict, 0, MAX_COLS_DICT );
for ( XP_Bool result = di_firstWord( &iter );
DictIter* iter = patsParamsToIter( params, dict );
const XP_UCHAR* delim = params->dumpDelim; /* NULL is ok */
for ( XP_Bool result = di_firstWord( iter );
result;
result = di_getNextWord( &iter ) ) {
result = di_getNextWord( iter ) ) {
XP_UCHAR buf[32];
di_wordToString( &iter, buf, VSIZE(buf), "." );
di_wordToString( iter, buf, VSIZE(buf), delim );
fprintf( stdout, "%s\n", buf );
}
di_freeIter( iter, NULL_XWE );
}
static void
@ -2545,13 +2601,36 @@ dawg2dict( const LaunchParams* params, GSList* testDicts )
g_slist_nth_data( testDicts, ii ),
params->useMmap );
if ( NULL != dict ) {
dumpDict( dict );
dumpDict( params, dict );
dict_unref( dict, NULL_XWE );
}
}
return 0;
}
#ifdef XWFEATURE_TESTPATSTR
static int
testOneString( const LaunchParams* params, GSList* testDicts )
{
int result = 0;
guint count = g_slist_length( testDicts );
for ( int ii = 0; 0 == result && ii < count; ++ii ) {
DictionaryCtxt* dict =
linux_dictionary_make( MPPARM(params->mpool) NULL_XWE, params,
g_slist_nth_data( testDicts, ii ),
params->useMmap );
if ( NULL != dict ) {
DictIter* iter = patsParamsToIter( params, dict );
if ( ! di_stringMatches( iter, params->iterTestPatStr ) ) {
result = 1;
}
di_freeIter( iter, NULL_XWE );
}
}
return result;
}
#endif
int
main( int argc, char** argv )
{
@ -2570,7 +2649,6 @@ main( int argc, char** argv )
#ifdef XWFEATURE_WALKDICT
GSList* testDicts = NULL;
GSList* testPrefixes = NULL;
char* testMinMax = NULL;
#endif
char dictbuf[256];
char* dict;
@ -2730,7 +2808,18 @@ main( int argc, char** argv )
testPrefixes = g_slist_prepend( testPrefixes, g_strdup(optarg) );
break;
case CMD_TESTMINMAX:
testMinMax = optarg;
mainParams.testMinMax = optarg;
break;
#endif
case CMD_DELIM:
mainParams.dumpDelim = optarg;
break;
#ifdef XWFEATURE_TESTPATSTR
case CMD_TESTPAT:
mainParams.iterTestPats = g_slist_append( mainParams.iterTestPats, optarg );
break;
case CMD_TESTSTR:
mainParams.iterTestPatStr = optarg;
break;
#endif
case CMD_DICTDIR:
@ -3104,18 +3193,22 @@ main( int argc, char** argv )
}
}
/* add cur dir if dict search dir path is empty */
if ( !mainParams.dictDirs ) {
mainParams.dictDirs = g_slist_append( mainParams.dictDirs, "./" );
}
int result = 0;
if ( g_str_has_suffix( argv[0], "dawg2dict" ) ) {
result = dawg2dict( &mainParams, testDicts );
#ifdef XWFEATURE_TESTPATSTR
} else if ( !!mainParams.iterTestPatStr ) {
result = testOneString( &mainParams, testDicts );
#endif
} else {
XP_ASSERT( mainParams.pgi.nPlayers == mainParams.nLocalPlayers
+ mainParams.info.serverInfo.nRemotePlayers );
/* add cur dir if dict search dir path is empty */
if ( !mainParams.dictDirs ) {
mainParams.dictDirs = g_slist_append( mainParams.dictDirs, "./" );
}
if ( mainParams.info.serverInfo.nRemotePlayers == 0 ) {
mainParams.pgi.serverRole = SERVER_STANDALONE;
} else if ( isServer ) {
@ -3199,8 +3292,7 @@ main( int argc, char** argv )
/* } */
#ifdef XWFEATURE_WALKDICT
if ( !!testDicts ) {
walk_dict_test_all( MPPARM(mainParams.mpool) &mainParams, testDicts,
testPrefixes, testMinMax );
walk_dict_test_all( MPPARM(mainParams.mpool) &mainParams, testDicts, testPrefixes );
exit( 0 );
}
#endif

View file

@ -140,6 +140,18 @@ typedef struct _LaunchParams {
DeviceRole serverRole;
const XP_UCHAR* testMinMax;
const XP_UCHAR* dumpDelim;
GSList* iterTestPats;
/* These three aren't used yet */
const XP_UCHAR* patStartW;
const XP_UCHAR* patContains;
const XP_UCHAR* patEndsW;
#ifdef XWFEATURE_TESTPATSTR
const XP_UCHAR* iterTestPatStr;
#endif
CommsAddrRec addr;
struct {
XP_U16 inviteeCounts[MAX_NUM_PLAYERS];

View file

@ -57,7 +57,7 @@ loadClientID( LaunchParams* params, MQTTConStorage* storage )
}
static void
onMessageReceived( struct mosquitto *mosq, void *userdata,
onMessageReceived( struct mosquitto* XP_UNUSED_DBG(mosq), void *userdata,
const struct mosquitto_message* message )
{
XP_LOGFF( "(len=%d)", message->payloadlen );
@ -74,7 +74,7 @@ onMessageReceived( struct mosquitto *mosq, void *userdata,
}
static void
connect_callback( struct mosquitto *mosq, void *userdata, int err )
connect_callback( struct mosquitto *mosq, void *userdata, int XP_UNUSED_DBG(err) )
{
XP_LOGFF( "(err=%s)", mosquitto_strerror(err) );
XP_USE(mosq);
@ -104,7 +104,8 @@ subscribe_callback( struct mosquitto *mosq, void *userdata, int mid,
}
static void
log_callback( struct mosquitto *mosq, void *userdata, int level, const char *str )
log_callback( struct mosquitto *mosq, void *userdata, int level,
const char* XP_UNUSED_DBG(str) )
{
XP_USE(mosq);
XP_USE(userdata);
@ -122,11 +123,17 @@ handle_gotmsg( GIOChannel* source, GIOCondition XP_UNUSED(condition), gpointer d
int pipe = g_io_channel_unix_get_fd( source );
XP_ASSERT( pipe == storage->msgPipe[0] );
short len;
ssize_t nRead = read( pipe, &len, sizeof(len) );
#ifdef DEBUG
ssize_t nRead =
#endif
read( pipe, &len, sizeof(len) );
XP_ASSERT( nRead == sizeof(len) );
len = ntohs(len);
XP_U8 buf[len];
nRead = read( pipe, buf, len );
#ifdef DEBUG
nRead =
#endif
read( pipe, buf, len );
XP_ASSERT( nRead == len );
dvc_parseMQTTPacket( storage->params->dutil, NULL_XWE, buf, len );
@ -167,8 +174,10 @@ mqttc_init( LaunchParams* params )
storage->params = params;
loadClientID( params, storage );
int res = pipe( storage->msgPipe );
#ifdef DEBUG
int res =
#endif
pipe( storage->msgPipe );
XP_ASSERT( !res );
ADD_SOCKET( storage, storage->msgPipe[0], handle_gotmsg );
@ -209,8 +218,10 @@ void
mqttc_cleanup( LaunchParams* params )
{
MQTTConStorage* storage = getStorage( params );
int err = mosquitto_loop_stop( storage->mosq, true ); /* blocks until thread dies */
#ifdef DEBUG
int err =
#endif
mosquitto_loop_stop( storage->mosq, true ); /* blocks until thread dies */
XP_LOGFF( "mosquitto_loop_stop() => %s", mosquitto_strerror(err) );
mosquitto_destroy( storage->mosq );
storage->mosq = NULL;
@ -239,9 +250,11 @@ void
mqttc_invite( LaunchParams* params, NetLaunchInfo* nli, const MQTTDevID* invitee )
{
MQTTConStorage* storage = getStorage( params );
#ifdef DEBUG
gchar buf[32];
XP_LOGFF( "need to send to %s", formatMQTTDevID(invitee, buf, sizeof(buf) ) );
XP_ASSERT( 16 == strlen(buf) );
#endif
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
params->vtMgr );

View file

@ -0,0 +1,92 @@
#!/usr/bin/env python3
import argparse, subprocess
g_pairs = [
(['_*ING'], [('RINGER', False)]),
(['_{2}ING'], [('RING', False), ('DOING', True), ('SPRING', False)]),
(['_{2}'], [('DO', True)]),
(['D_{1,2}'], [('DOG', True), ('DO', True), ('D', False)]),
(['A_*'], [('ABLE', True), ('BALL', False),]),
(['B_*'], [('BALL', True), ('APPLE', False), ('CAT', False),],),
(['ABC'], [('ABC', True), ('CBA', False)]),
(['_*'], [('ABC', True)]),
(['_*Z'], [('ABC', False), ('AB', False)]),
(['_*C'], [('ABC', True)]),
(['_*B'], [('AB', True)]),
(['B_*'], [('AB', False), ('BA', True)]),
(['A*'], [('A', True)]),
(['A*A*'], [('A', True), ('AA', True), ('AAA', True), ('AAAA', True), ('AABA', False)]),
(['A*A*B'], [('B', True), ('C', False)]),
(['A{3}'], [('AAA', True)]),
(['A{1}A{1}A{1}'], [('AAA', True),('AA', False),('AAAA', False)]),
(['A*A*A*'], [('A', True),('AA', True),('AAA', True),('AAAA', True), ('ABAA', False)]),
(['AB*'], [('A', True)]),
(['A{2,4}'], [('A', False), ('AA', True), ('AAAA', True), ('AAAAA', False)]),
(['_*ING'], [('RINGER', False)]),
(['R_*'], [('RINGER', True)]),
(['R_*', '_*ING'], [('RING', True), ('ROLLING', True), ('SPRING', False), ('INGER', False), ('RINGER', False)]),
(['A', '_*'], [('ABC', False), ('CBA', False), ('A', True)]),
(['ABC', '_*'], [('ABC', True)]),
(['[ABC]{3}'], [('ABC', True), ('CBA', True), ('AAA', True), ('BBB', True)]),
(['[+ABC]{3}'], [('ABC', True), ('CBA', True), ('AAA', False), ('AA', False), ]),
(['[+ABC]{3}_*'], [('ABC', True), ('CBA', True), ('AAA', False), ('AA', False), ]),
(['AA[+ABC]{3}ZZ'], [('AAABCZZ', True), ('AACBAZZ', True), ('AAAAAZZ', False), ]),
(['[+AAB]{3}'], [('AAB', True), ('ABA', True), ('ABB', False), ('AB', False),
('ABBA', False),]),
(['[+AB_]{3}'], [('AAB', True), ('ABA', True), ('ABB', True), ('ABC', True),
('AB', False), ('ABBA', False),]),
(['[+_AB]{3}'], [('AAB', True), ('ABA', True), ('ABB', True), ('ABC', True),
('AB', False), ('ABBA', False),]),
]
g_dict = '../android/app/src/main/assets/BasEnglish2to8.xwd'
def doTest( pats, pair, stderr ):
# print('pair: {}'.format(pair))
(word, expect) = pair
args = ['./obj_linux_memdbg/xwords', '--test-dict', g_dict, '--test-string', word ]
for pat in pats:
args += ['--test-pat', pat]
result = subprocess.run(args, stderr=stderr)
passed = (0 == result.returncode) == expect
return passed
def main():
stderr = subprocess.DEVNULL
parser = argparse.ArgumentParser()
parser.add_argument('--with-stderr', dest = 'STDERR', action = 'store_true',
help = 'don\'t discard stderr')
parser.add_argument('--do-only', dest = 'SPECIFIED', type = int, action = 'append',
help = 'do this test case only')
parser.add_argument('--show-all', dest = 'PRINT_ALL', action = 'store_true', default = False,
help = 'print successes in addition to failures')
args = parser.parse_args()
if args.STDERR: stderr = None
ii = 0
counts = { False: 0, True: 0 }
for cases in g_pairs:
(pats, rest) = cases
for one in rest:
if not args.SPECIFIED or ii in args.SPECIFIED:
success = doTest(pats, one, stderr)
note = success and 'passed' or 'FAILED'
if args.PRINT_ALL or not success:
print( '{:2d} {}: {} {}'.format(ii, note, pats, one))
counts[success] += 1
ii += 1
print('Successes: {}'.format(counts[True]))
print('Failures: {}'.format(counts[False]))
##############################################################################
if __name__ == '__main__':
main()