X-Git-Url: http://gitweb.fperrin.net/?p=Dictionary.git;a=blobdiff_plain;f=src%2Fcom%2Fhughes%2Fandroid%2Fdictionary%2FDictionaryActivity.java;h=e2558a0773605615ff629cb7aab682a9605a2de3;hp=e0290b065eb1e8cc057e5091c6cdc170494df368;hb=c76660b2772122109529d3616289980a7084eeeb;hpb=3d5bebe3bccd4eca654c95c1dbee924d31792339 diff --git a/src/com/hughes/android/dictionary/DictionaryActivity.java b/src/com/hughes/android/dictionary/DictionaryActivity.java index e0290b0..e2558a0 100644 --- a/src/com/hughes/android/dictionary/DictionaryActivity.java +++ b/src/com/hughes/android/dictionary/DictionaryActivity.java @@ -20,25 +20,34 @@ import android.app.SearchManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.database.MatrixCursor; import android.graphics.Color; import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.preference.PreferenceManager; +import android.support.v7.preference.PreferenceManager; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech.OnInitListener; +import android.support.annotation.NonNull; +import android.support.design.widget.FloatingActionButton; import android.support.v4.view.MenuItemCompat; +import android.support.v4.widget.CursorAdapter; import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.SearchView; import android.support.v7.widget.SearchView.OnQueryTextListener; import android.support.v7.widget.Toolbar; import android.text.ClipboardManager; +import android.text.InputType; import android.text.Spannable; +import android.text.SpannableString; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.text.style.StyleSpan; +import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.ContextMenu; @@ -59,6 +68,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; +import android.widget.AutoCompleteTextView; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.FrameLayout; @@ -71,7 +81,6 @@ import android.widget.ListView; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; -import android.widget.TextView.BufferType; import android.widget.Toast; import com.hughes.android.dictionary.DictionaryInfo.IndexInfo; @@ -80,7 +89,6 @@ import com.hughes.android.dictionary.engine.EntrySource; import com.hughes.android.dictionary.engine.HtmlEntry; import com.hughes.android.dictionary.engine.Index; import com.hughes.android.dictionary.engine.Index.IndexEntry; -import com.hughes.android.dictionary.engine.Language.LanguageResources; import com.hughes.android.dictionary.engine.PairEntry; import com.hughes.android.dictionary.engine.PairEntry.Pair; import com.hughes.android.dictionary.engine.RowBase; @@ -95,7 +103,9 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -105,50 +115,54 @@ import java.util.List; import java.util.Locale; import java.util.Random; import java.util.Set; -import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class DictionaryActivity extends ActionBarActivity { +public class DictionaryActivity extends AppCompatActivity { - static final String LOG = "QuickDic"; + private static final String LOG = "QuickDic"; - DictionaryApplication application; + private DictionaryApplication application; - File dictFile = null; - RandomAccessFile dictRaf = null; + private File dictFile = null; + private FileChannel dictRaf = null; + private String dictFileTitleName = null; - Dictionary dictionary = null; + private Dictionary dictionary = null; - int indexIndex = 0; + private int indexIndex = 0; - Index index = null; + private Index index = null; - List rowsToShow = null; // if not null, just show these rows. + private List rowsToShow = null; // if not null, just show these rows. - final Random rand = new Random(); + private final Random rand = new Random(); - final Handler uiHandler = new Handler(); + private final Handler uiHandler = new Handler(); - private final Executor searchExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { + private final ExecutorService searchExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { @Override - public Thread newThread(Runnable r) { + public Thread newThread(@NonNull Runnable r) { return new Thread(r, "searchExecutor"); } }); private SearchOperation currentSearchOperation = null; + private final int MAX_SEARCH_HISTORY = 10; + private final ArrayList searchHistory = new ArrayList<>(MAX_SEARCH_HISTORY); + private MatrixCursor searchHistoryCursor = new MatrixCursor(new String[] {"_id", "search"}); - TextToSpeech textToSpeech; - volatile boolean ttsReady; + private TextToSpeech textToSpeech; + private volatile boolean ttsReady; - Typeface typeface; - DictionaryApplication.Theme theme = DictionaryApplication.Theme.LIGHT; - int textColorFg = Color.BLACK; - int fontSizeSp; + private Typeface typeface; + private DictionaryApplication.Theme theme = DictionaryApplication.Theme.LIGHT; + private int textColorFg = Color.BLACK; + private int fontSizeSp; private ListView listView; private ListView getListView() { @@ -166,11 +180,13 @@ public class DictionaryActivity extends ActionBarActivity { return getListView().getAdapter(); } - SearchView searchView; - ImageButton languageButton; - SearchView.OnQueryTextListener onQueryTextListener; + private SearchView searchView; + private AutoCompleteTextView searchTextView; + private ImageButton languageButton; + private SearchView.OnQueryTextListener onQueryTextListener; - MenuItem nextWordMenuItem, previousWordMenuItem, randomWordMenuItem; + private MenuItem nextWordMenuItem; + private MenuItem previousWordMenuItem; // Never null. private File wordList = null; @@ -178,7 +194,7 @@ public class DictionaryActivity extends ActionBarActivity { private boolean clickOpensContextMenu = false; // Visible for testing. - ListAdapter indexAdapter = null; + private ListAdapter indexAdapter = null; /** * For some languages, loading the transliterators used in this search takes @@ -191,11 +207,12 @@ public class DictionaryActivity extends ActionBarActivity { } public static Intent getLaunchIntent(Context c, final File dictFile, final String indexShortName, - final String searchToken) { + final String searchToken) { final Intent intent = new Intent(c, DictionaryActivity.class); intent.putExtra(C.DICT_FILE, dictFile.getPath()); intent.putExtra(C.INDEX_SHORT_NAME, indexShortName); intent.putExtra(C.SEARCH_TOKEN, searchToken); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); return intent; } @@ -205,14 +222,78 @@ public class DictionaryActivity extends ActionBarActivity { Log.d(LOG, "onSaveInstanceState: " + searchView.getQuery().toString()); outState.putString(C.INDEX_SHORT_NAME, index.shortName); outState.putString(C.SEARCH_TOKEN, searchView.getQuery().toString()); + outState.putStringArrayList(C.SEARCH_HISTORY, searchHistory); + } + + private int getMatchLen(String search, Index.IndexEntry e) { + if (e == null) return 0; + for (int i = 0; i < search.length(); ++i) { + String a = search.substring(0, i + 1); + String b = e.token.substring(0, i + 1); + if (!a.equalsIgnoreCase(b)) + return i; + } + return search.length(); + } + + private void dictionaryOpenFail(Exception e) { + Log.e(LOG, "Unable to load dictionary.", e); + if (dictRaf != null) { + indexAdapter = null; + setListAdapter(null); + try { + dictRaf.close(); + } catch (IOException e1) { + Log.e(LOG, "Unable to close dictRaf.", e1); + } + dictRaf = null; + } + if (!isFinishing()) + Toast.makeText(this, getString(R.string.invalidDictionary, "", e.getMessage()), + Toast.LENGTH_LONG).show(); + startActivity(DictionaryManagerActivity.getLaunchIntent(getApplicationContext())); + finish(); + } + + private void saveSearchHistory() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + final SharedPreferences.Editor ed = prefs.edit(); + for (int i = 0; i < searchHistory.size(); i++) { + ed.putString("history" + i, searchHistory.get(i)); + } + ed.remove("history" + searchHistory.size()); + ed.apply(); + } + + private void addToSearchHistory() { + addToSearchHistory(searchView.getQuery().toString()); + } + + private void addToSearchHistory(String text) { + if (text == null || text.isEmpty()) return; + int exists = searchHistory.indexOf(text); + if (exists >= 0) searchHistory.remove(exists); + else if (searchHistory.size() >= MAX_SEARCH_HISTORY) searchHistory.remove(searchHistory.size() - 1); + searchHistory.add(0, text); + searchHistoryCursor = new MatrixCursor(new String[] {"_id", "search"}); + for (int i = 0; i < searchHistory.size(); i++) { + final Object[] row = {i, searchHistory.get(i)}; + searchHistoryCursor.addRow(row); + } + if (searchView.getSuggestionsAdapter().getCursor() != null) { + searchView.getSuggestionsAdapter().swapCursor(searchHistoryCursor); + searchView.getSuggestionsAdapter().notifyDataSetChanged(); + } } @Override public void onCreate(Bundle savedInstanceState) { + DictionaryApplication.INSTANCE.init(getApplicationContext()); + application = DictionaryApplication.INSTANCE; // This needs to be before super.onCreate, otherwise ActionbarSherlock // doesn't makes the background of the actionbar white when you're // in the dark theme. - setTheme(((DictionaryApplication) getApplication()).getSelectedTheme().themeId); + setTheme(application.getSelectedTheme().themeId); Log.d(LOG, "onCreate:" + this); super.onCreate(savedInstanceState); @@ -220,25 +301,32 @@ public class DictionaryActivity extends ActionBarActivity { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); // Don't auto-launch if this fails. - prefs.edit().remove(C.DICT_FILE).commit(); + prefs.edit().remove(C.DICT_FILE).remove(C.INDEX_SHORT_NAME).commit(); setContentView(R.layout.dictionary_activity); - application = (DictionaryApplication) getApplication(); theme = application.getSelectedTheme(); textColorFg = getResources().getColor(theme.tokenRowFgColor); + if (dictRaf != null) { + try { + dictRaf.close(); + } catch (IOException e) { + Log.e(LOG, "Failed to close dictionary", e); + } + dictRaf = null; + } + final Intent intent = getIntent(); String intentAction = intent.getAction(); - /** - * @author Dominik Köppl Querying the Intent + /* + @author Dominik Köppl Querying the Intent * com.hughes.action.ACTION_SEARCH_DICT is the advanced query * Arguments: SearchManager.QUERY -> the phrase to search from * -> language in which the phrase is written to -> to which * language shall be translated */ - if (intentAction != null && intentAction.equals("com.hughes.action.ACTION_SEARCH_DICT")) - { + if ("com.hughes.action.ACTION_SEARCH_DICT".equals(intentAction)) { String query = intent.getStringExtra(SearchManager.QUERY); String from = intent.getStringExtra("from"); if (from != null) @@ -246,81 +334,134 @@ public class DictionaryActivity extends ActionBarActivity { String to = intent.getStringExtra("to"); if (to != null) to = to.toLowerCase(Locale.US); - if (query != null) - { + if (query != null) { getIntent().putExtra(C.SEARCH_TOKEN, query); } - if (intent.getStringExtra(C.DICT_FILE) == null && (from != null || to != null)) - { + if (intent.getStringExtra(C.DICT_FILE) == null && (from != null || to != null)) { Log.d(LOG, "DictSearch: from: " + from + " to " + to); List dicts = application.getDictionariesOnDevice(null); - for (DictionaryInfo info : dicts) - { + for (DictionaryInfo info : dicts) { boolean hasFrom = from == null; boolean hasTo = to == null; - for (IndexInfo index : info.indexInfos) - { + for (IndexInfo index : info.indexInfos) { if (!hasFrom && index.shortName.toLowerCase(Locale.US).equals(from)) hasFrom = true; if (!hasTo && index.shortName.toLowerCase(Locale.US).equals(to)) hasTo = true; } - if (hasFrom && hasTo) - { - if (from != null) - { + if (hasFrom && hasTo) { + if (from != null) { int which_index = 0; - for (; which_index < info.indexInfos.size(); ++which_index) - { + for (; which_index < info.indexInfos.size(); ++which_index) { if (info.indexInfos.get(which_index).shortName.toLowerCase( - Locale.US).equals(from)) + Locale.US).equals(from)) break; } intent.putExtra(C.INDEX_SHORT_NAME, - info.indexInfos.get(which_index).shortName); + info.indexInfos.get(which_index).shortName); } intent.putExtra(C.DICT_FILE, application.getPath(info.uncompressedFilename) - .toString()); + .toString()); break; } } } } - /** - * @author Dominik Köppl Querying the Intent Intent.ACTION_SEARCH is a + /* + @author Dominik Köppl Querying the Intent Intent.ACTION_SEARCH is a * simple query Arguments follow from android standard (see * documentation) */ - if (intentAction != null && intentAction.equals(Intent.ACTION_SEARCH)) - { + if (intentAction != null && intentAction.equals(Intent.ACTION_SEARCH)) { String query = intent.getStringExtra(SearchManager.QUERY); if (query != null) getIntent().putExtra(C.SEARCH_TOKEN, query); } - /** - * @author Dominik Köppl If no dictionary is chosen, use the default + if (intentAction != null && intentAction.equals(Intent.ACTION_SEND)) { + String query = intent.getStringExtra(Intent.EXTRA_TEXT); + if (query != null) + getIntent().putExtra(C.SEARCH_TOKEN, query); + } + /* + * This processes text on M+ devices where QuickDic shows up in the context menu. + */ + if (intentAction != null && intentAction.equals(Intent.ACTION_PROCESS_TEXT)) { + String query = intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT); + if (query != null) { + getIntent().putExtra(C.SEARCH_TOKEN, query); + } + } + // Support opening dictionary file directly + if (intentAction != null && intentAction.equals(Intent.ACTION_VIEW)) { + Uri uri = intent.getData(); + intent.putExtra(C.DICT_FILE, uri.toString()); + dictFileTitleName = uri.getLastPathSegment(); + try { + dictRaf = getContentResolver().openAssetFileDescriptor(uri, "r").createInputStream().getChannel(); + } catch (Exception e) { + dictionaryOpenFail(e); + return; + } + } + /* + @author Dominik Köppl If no dictionary is chosen, use the default * dictionary specified in the preferences If this step does - * fail (no default directory specified), show a toast and + * fail (no default dictionary specified), show a toast and * abort. */ - if (intent.getStringExtra(C.DICT_FILE) == null) - { + if (intent.getStringExtra(C.DICT_FILE) == null) { String dictfile = prefs.getString(getString(R.string.defaultDicKey), null); if (dictfile != null) intent.putExtra(C.DICT_FILE, application.getPath(dictfile).toString()); } String dictFilename = intent.getStringExtra(C.DICT_FILE); + if (dictFilename == null && intent.getStringExtra(C.SEARCH_TOKEN) != null) { + final List dics = application.getDictionariesOnDevice(null); + final String search = intent.getStringExtra(C.SEARCH_TOKEN); + String bestFname = null; + String bestIndex = null; + int bestMatchLen = 2; // ignore shorter matches + AtomicBoolean dummy = new AtomicBoolean(); + for (int i = 0; dictFilename == null && i < dics.size(); ++i) { + try { + Log.d(LOG, "Checking dictionary " + dics.get(i).uncompressedFilename); + final File dictfile = application.getPath(dics.get(i).uncompressedFilename); + Dictionary dic = new Dictionary(new RandomAccessFile(dictfile, "r").getChannel()); + for (int j = 0; j < dic.indices.size(); ++j) { + Index idx = dic.indices.get(j); + Log.d(LOG, "Checking index " + idx.shortName); + if (idx.findExact(search) != null) { + Log.d(LOG, "Found exact match"); + dictFilename = dictfile.toString(); + intent.putExtra(C.INDEX_SHORT_NAME, idx.shortName); + break; + } + int matchLen = getMatchLen(search, idx.findInsertionPoint(search, dummy)); + Log.d(LOG, "Found partial match length " + matchLen); + if (matchLen > bestMatchLen) { + bestFname = dictfile.toString(); + bestIndex = idx.shortName; + bestMatchLen = matchLen; + } + } + } catch (Exception e) {} + } + if (dictFilename == null && bestFname != null) { + dictFilename = bestFname; + intent.putExtra(C.INDEX_SHORT_NAME, bestIndex); + } + } - if (dictFilename == null) - { - Toast.makeText(this, getString(R.string.no_dict_file), Toast.LENGTH_LONG).show(); + if (dictFilename == null) { + if (!isFinishing()) + Toast.makeText(this, getString(R.string.no_dict_file), Toast.LENGTH_LONG).show(); startActivity(DictionaryManagerActivity.getLaunchIntent(getApplicationContext())); finish(); return; } - if (dictFilename != null) + if (dictRaf == null) dictFile = new File(dictFilename); ttsReady = false; @@ -328,29 +469,19 @@ public class DictionaryActivity extends ActionBarActivity { @Override public void onInit(int status) { ttsReady = true; - updateTTSLanguage(); + updateTTSLanguage(indexIndex); } }); try { - final String name = application.getDictionaryName(dictFile.getName()); - this.setTitle("QuickDic: " + name); - dictRaf = new RandomAccessFile(dictFile, "r"); + if (dictRaf == null) { + dictFileTitleName = application.getDictionaryName(dictFile.getName()); + dictRaf = new RandomAccessFile(dictFile, "r").getChannel(); + } + this.setTitle("QuickDic: " + dictFileTitleName); dictionary = new Dictionary(dictRaf); } catch (Exception e) { - Log.e(LOG, "Unable to load dictionary.", e); - if (dictRaf != null) { - try { - dictRaf.close(); - } catch (IOException e1) { - Log.e(LOG, "Unable to close dictRaf.", e1); - } - dictRaf = null; - } - Toast.makeText(this, getString(R.string.invalidDictionary, "", e.getMessage()), - Toast.LENGTH_LONG).show(); - startActivity(DictionaryManagerActivity.getLaunchIntent(getApplicationContext())); - finish(); + dictionaryOpenFail(e); return; } String targetIndex = intent.getStringExtra(C.INDEX_SHORT_NAME); @@ -366,62 +497,79 @@ public class DictionaryActivity extends ActionBarActivity { } Log.d(LOG, "Loading index " + indexIndex); index = dictionary.indices.get(indexIndex); + getListView().setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + getListView().setEmptyView(findViewById(android.R.id.empty)); + getListView().setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int row, long id) { + onListItemClick(getListView(), view, row, id); + } + }); + setListAdapter(new IndexAdapter(index)); + // Pre-load the Transliterator (will spawn its own thread) + TransliteratorManager.init(new TransliteratorManager.Callback() { + @Override + public void onTransliteratorReady() { + uiHandler.post(new Runnable() { + @Override + public void run() { + onSearchTextChange(searchView.getQuery().toString()); + } + }); + } + }, DictionaryApplication.threadBackground); + // Pre-load the collators. new Thread(new Runnable() { public void run() { - android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); + android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE); final long startMillis = System.currentTimeMillis(); try { - TransliteratorManager.init(new TransliteratorManager.Callback() { - @Override - public void onTransliteratorReady() { - uiHandler.post(new Runnable() { - @Override - public void run() { - onSearchTextChange(searchView.getQuery().toString()); - } - }); - } - }); - for (final Index index : dictionary.indices) { final String searchToken = index.sortedIndexEntries.get(0).token; final IndexEntry entry = index.findExact(searchToken); - if (!searchToken.equals(entry.token)) { - Log.e(LOG, "Couldn't find token: " + searchToken + ", " + entry.token); + if (entry == null || !searchToken.equals(entry.token)) { + Log.e(LOG, "Couldn't find token: " + searchToken + ", " + (entry == null ? "null" : entry.token)); } } indexPrepFinished = true; } catch (Exception e) { Log.w(LOG, - "Exception while prepping. This can happen if dictionary is closed while search is happening."); + "Exception while prepping. This can happen if dictionary is closed while search is happening."); } Log.d(LOG, "Prepping indices took:" + (System.currentTimeMillis() - startMillis)); } }).start(); String fontName = prefs.getString(getString(R.string.fontKey), "FreeSerif.otf.jpg"); - if ("SYSTEM".equals(fontName)) { - typeface = Typeface.DEFAULT; - } else if ("SERIF".equals(fontName)) { - typeface = Typeface.SERIF; - } else if ("SANS_SERIF".equals(fontName)) { - typeface = Typeface.SANS_SERIF; - } else if ("MONOSPACE".equals(fontName)) { - typeface = Typeface.MONOSPACE; - } else { - if ("FreeSerif.ttf.jpg".equals(fontName)) { - fontName = "FreeSerif.otf.jpg"; - } - try { - typeface = Typeface.createFromAsset(getAssets(), fontName); - } catch (Exception e) { - Log.w(LOG, "Exception trying to use typeface, using default.", e); - Toast.makeText(this, getString(R.string.fontFailure, e.getLocalizedMessage()), - Toast.LENGTH_LONG).show(); - } + switch (fontName) { + case "SYSTEM": + typeface = Typeface.DEFAULT; + break; + case "SERIF": + typeface = Typeface.SERIF; + break; + case "SANS_SERIF": + typeface = Typeface.SANS_SERIF; + break; + case "MONOSPACE": + typeface = Typeface.MONOSPACE; + break; + default: + if ("FreeSerif.ttf.jpg".equals(fontName)) { + fontName = "FreeSerif.otf.jpg"; + } + try { + typeface = Typeface.createFromAsset(getAssets(), fontName); + } catch (Exception e) { + Log.w(LOG, "Exception trying to use typeface, using default.", e); + if (!isFinishing()) + Toast.makeText(this, getString(R.string.fontFailure, e.getLocalizedMessage()), + Toast.LENGTH_LONG).show(); + } + break; } if (typeface == null) { Log.w(LOG, "Unable to create typeface, using default."); @@ -440,13 +588,22 @@ public class DictionaryActivity extends ActionBarActivity { // Cache some prefs. wordList = application.getWordListFile(); saveOnlyFirstSubentry = prefs.getBoolean(getString(R.string.saveOnlyFirstSubentryKey), - false); + false); clickOpensContextMenu = prefs.getBoolean(getString(R.string.clickOpensContextMenuKey), - false); + !getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)); Log.d(LOG, "wordList=" + wordList + ", saveOnlyFirstSubentry=" + saveOnlyFirstSubentry); onCreateSetupActionBarAndSearchView(); + View floatSwapButton = findViewById(R.id.floatSwapButton); + floatSwapButton.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + onLanguageButtonLongClick(v.getContext()); + return true; + } + }); + // Set the search text from the intent, then the saved state. String text = getIntent().getStringExtra(C.SEARCH_TOKEN); if (savedInstanceState != null) { @@ -455,10 +612,58 @@ public class DictionaryActivity extends ActionBarActivity { if (text == null) { text = ""; } + + searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() { + @Override + public boolean onSuggestionSelect(int position) { + return false; + } + + @Override + public boolean onSuggestionClick(int position) { + String h = searchHistory.get(position); + addToSearchHistory(h); + setSearchText(h, true); + return true; + } + }); + searchView.setSuggestionsAdapter(new CursorAdapter(this, text.isEmpty() ? searchHistoryCursor : null, 0) { + @Override + public View newView(Context context, Cursor c, ViewGroup p) { + TextView v = new TextView(context); + v.setTextColor(textColorFg); + v.setTypeface(typeface); + v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 4 * fontSizeSp / 3); + return v; + } + @Override + public void bindView(View v, Context context, Cursor c) { + TextView t = (TextView)v; + t.setText(c.getString(1)); + } + }); + + // Set up search history + ArrayList savedHistory = null; + if (savedInstanceState != null) savedHistory = savedInstanceState.getStringArrayList(C.SEARCH_HISTORY); + if (savedHistory != null && !savedHistory.isEmpty()) { + } else { + savedHistory = new ArrayList<>(); + for (int i = 0; i < MAX_SEARCH_HISTORY; i++) { + String h = prefs.getString("history" + i, null); + if (h == null) break; + savedHistory.add(h); + } + } + for (int i = savedHistory.size() - 1; i >= 0; i--) { + addToSearchHistory(savedHistory.get(i)); + } + addToSearchHistory(text); + setSearchText(text, true); Log.d(LOG, "Trying to restore searchText=" + text); - setDictionaryPrefs(this, dictFile, index.shortName, searchView.getQuery().toString()); + setDictionaryPrefs(this, dictFile, index.shortName); updateLangButton(); searchView.requestFocus(); @@ -472,35 +677,20 @@ public class DictionaryActivity extends ActionBarActivity { actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowHomeEnabled(false); actionBar.setDisplayHomeAsUpEnabled(false); - + final LinearLayout customSearchView = new LinearLayout(getSupportActionBar().getThemedContext()); - - final int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300, - getResources().getDisplayMetrics()); + final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); customSearchView.setLayoutParams(layoutParams); - listView.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int row, long id) { - onListItemClick(getListView(), view, row, id); - } - }); - languageButton = new ImageButton(customSearchView.getContext()); + languageButton.setId(R.id.languageButton); languageButton.setScaleType(ScaleType.FIT_CENTER); languageButton.setOnClickListener(new OnClickListener() { @Override - public void onClick(View arg0) { - onLanguageButtonClick(); - } - }); - languageButton.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { + public void onClick(View v) { onLanguageButtonLongClick(v.getContext()); - return true; } }); languageButton.setAdjustViewBounds(true); @@ -508,23 +698,28 @@ public class DictionaryActivity extends ActionBarActivity { customSearchView.addView(languageButton, lpb); searchView = new SearchView(getSupportActionBar().getThemedContext()); - searchView.setIconifiedByDefault(false); - // searchView.setIconified(false); // puts the magnifying glass in the - // wrong place. + searchView.setId(R.id.searchView); + + // Get rid of search icon, it takes up too much space. + // There is still text saying "search" in the search field. + searchView.setIconifiedByDefault(true); + searchView.setIconified(false); + searchView.setQueryHint(getString(R.string.searchText)); searchView.setSubmitButtonEnabled(false); + searchView.setInputType(InputType.TYPE_CLASS_TEXT); searchView.setImeOptions( - EditorInfo.IME_ACTION_SEARCH | - EditorInfo.IME_FLAG_NO_EXTRACT_UI | - EditorInfo.IME_FLAG_NO_ENTER_ACTION | - // EditorInfo.IME_FLAG_NO_FULLSCREEN | // Requires API - // 11 - EditorInfo.IME_MASK_ACTION | - EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + EditorInfo.IME_ACTION_DONE | + EditorInfo.IME_FLAG_NO_EXTRACT_UI | + // EditorInfo.IME_FLAG_NO_FULLSCREEN | // Requires API + // 11 + EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS); onQueryTextListener = new OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { Log.d(LOG, "OnQueryTextListener: onQueryTextSubmit: " + searchView.getQuery()); + addToSearchHistory(); + hideKeyboard(); return true; } @@ -537,6 +732,7 @@ public class DictionaryActivity extends ActionBarActivity { }; searchView.setOnQueryTextListener(onQueryTextListener); searchView.setFocusable(true); + searchTextView = (AutoCompleteTextView)searchView.findViewById(android.support.v7.appcompat.R.id.search_src_text); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, FrameLayout.LayoutParams.WRAP_CONTENT, 1); customSearchView.addView(searchView, lp); @@ -544,9 +740,13 @@ public class DictionaryActivity extends ActionBarActivity { actionBar.setCustomView(customSearchView); actionBar.setDisplayShowCustomEnabled(true); - // Avoid wasting space on large left inset + // Avoid wasting space on large left inset Toolbar tb = (Toolbar)customSearchView.getParent(); tb.setContentInsetsRelative(0, 0); + + getListView().setNextFocusLeftId(R.id.searchView); + findViewById(R.id.floatSwapButton).setNextFocusRightId(R.id.languageButton); + languageButton.setNextFocusLeftId(R.id.floatSwapButton); } @Override @@ -561,16 +761,11 @@ public class DictionaryActivity extends ActionBarActivity { showKeyboard(); } - @Override - protected void onPause() { - super.onPause(); - } - - @Override /** * Invoked when MyWebView returns, since the user might have clicked some * hypertext in the MyWebView. */ + @Override protected void onActivityResult(int requestCode, int resultCode, Intent result) { super.onActivityResult(requestCode, resultCode, result); if (result != null && result.hasExtra(C.SEARCH_TOKEN)) { @@ -580,15 +775,24 @@ public class DictionaryActivity extends ActionBarActivity { } private static void setDictionaryPrefs(final Context context, final File dictFile, - final String indexShortName, final String searchToken) { + final String indexShortName) { final SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences( context).edit(); - prefs.putString(C.DICT_FILE, dictFile.getPath()); - prefs.putString(C.INDEX_SHORT_NAME, indexShortName); - prefs.putString(C.SEARCH_TOKEN, ""); // Don't need to save search token. + if (dictFile != null) { + prefs.putString(C.DICT_FILE, dictFile.getPath()); + prefs.putString(C.INDEX_SHORT_NAME, indexShortName); + } + prefs.remove(C.SEARCH_TOKEN); // Don't need to save search token. prefs.commit(); } + @Override + protected void onPause() { + super.onPause(); + addToSearchHistory(); + saveSearchHistory(); + } + @Override protected void onDestroy() { super.onDestroy(); @@ -605,6 +809,12 @@ public class DictionaryActivity extends ActionBarActivity { currentSearchOperation = null; searchOperation.interrupted.set(true); } + searchExecutor.shutdownNow(); + textToSpeech.shutdown(); + textToSpeech = null; + + indexAdapter = null; + setListAdapter(null); try { Log.d(LOG, "Closing RAF."); @@ -630,21 +840,36 @@ public class DictionaryActivity extends ActionBarActivity { Log.d(LOG, "Trying to show soft keyboard."); final boolean searchTextHadFocus = searchView.hasFocus(); searchView.requestFocusFromTouch(); + searchTextView.requestFocus(); final InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); manager.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT); + manager.showSoftInput(searchTextView, InputMethodManager.SHOW_IMPLICIT); if (!searchTextHadFocus) { defocusSearchText(); } } }, delay); } + searchView.post(new Runnable() { + @Override + public void run() { + searchTextView.setThreshold(0); + searchTextView.showDropDown(); + } + }); } - void updateLangButton() { - final LanguageResources languageResources = - DictionaryApplication.isoCodeToResources.get(index.shortName); - if (languageResources != null && languageResources.flagId != 0) { - languageButton.setImageResource(languageResources.flagId); + private void hideKeyboard() { + Log.d(LOG, "Hide soft keyboard."); + searchView.clearFocus(); + InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + manager.hideSoftInputFromWindow(searchView.getWindowToken(), 0); + } + + private void updateLangButton() { + final int flagId = IsoUtils.INSTANCE.getFlagIdForIsoCode(index.shortName); + if (flagId != 0) { + languageButton.setImageResource(flagId); } else { if (indexIndex % 2 == 0) { languageButton.setImageResource(android.R.drawable.ic_media_next); @@ -652,24 +877,41 @@ public class DictionaryActivity extends ActionBarActivity { languageButton.setImageResource(android.R.drawable.ic_media_previous); } } - updateTTSLanguage(); + updateTTSLanguage(indexIndex); } - private void updateTTSLanguage() { + private void updateTTSLanguage(int i) { if (!ttsReady || index == null || textToSpeech == null) { Log.d(LOG, "Can't updateTTSLanguage."); return; } - final Locale locale = new Locale(index.sortLanguage.getIsoCode()); + final Locale locale = new Locale(dictionary.indices.get(i).sortLanguage.getIsoCode()); Log.d(LOG, "Setting TTS locale to: " + locale); - final int ttsResult = textToSpeech.setLanguage(locale); - if (ttsResult != TextToSpeech.LANG_AVAILABLE || - ttsResult != TextToSpeech.LANG_COUNTRY_AVAILABLE) { - Log.e(LOG, "TTS not available in this language: ttsResult=" + ttsResult); + try { + final int ttsResult = textToSpeech.setLanguage(locale); + if (ttsResult != TextToSpeech.LANG_AVAILABLE && + ttsResult != TextToSpeech.LANG_COUNTRY_AVAILABLE) { + Log.e(LOG, "TTS not available in this language: ttsResult=" + ttsResult); + } + } catch (Exception e) { + if (!isFinishing()) + Toast.makeText(this, getString(R.string.TTSbroken), Toast.LENGTH_LONG).show(); } } - void onLanguageButtonClick() { + public void onSearchButtonClick(View dummy) { + if (!searchView.hasFocus()) { + searchView.requestFocus(); + } + if (searchView.getQuery().toString().length() > 0) { + addToSearchHistory(); + searchView.setQuery("", false); + } + showKeyboard(); + searchView.setIconified(false); + } + + public void onLanguageButtonClick(View dummy) { if (dictionary.indices.size() == 1) { // No need to work to switch indices. return; @@ -679,22 +921,22 @@ public class DictionaryActivity extends ActionBarActivity { currentSearchOperation = null; } setIndexAndSearchText((indexIndex + 1) % dictionary.indices.size(), - searchView.getQuery().toString()); + searchView.getQuery().toString(), false); } - void onLanguageButtonLongClick(final Context context) { + private void onLanguageButtonLongClick(final Context context) { final Dialog dialog = new Dialog(context); dialog.setContentView(R.layout.select_dictionary_dialog); dialog.setTitle(R.string.selectDictionary); final List installedDicts = application.getDictionariesOnDevice(null); - ListView listView = (ListView) dialog.findViewById(android.R.id.list); + ListView listView = dialog.findViewById(android.R.id.list); final Button button = new Button(listView.getContext()); final String name = getString(R.string.dictionaryManager); button.setText(name); final IntentLauncher intentLauncher = new IntentLauncher(listView.getContext(), - DictionaryManagerActivity.getLaunchIntent(getApplicationContext())) { + DictionaryManagerActivity.getLaunchIntent(getApplicationContext())) { @Override protected void onGo() { dialog.dismiss(); @@ -704,6 +946,7 @@ public class DictionaryActivity extends ActionBarActivity { button.setOnClickListener(intentLauncher); listView.addHeaderView(button); + listView.setItemsCanFocus(true); listView.setAdapter(new BaseAdapter() { @Override public View getView(int position, View convertView, ViewGroup parent) { @@ -713,12 +956,12 @@ public class DictionaryActivity extends ActionBarActivity { for (int i = 0; i < dictionaryInfo.indexInfos.size(); ++i) { final IndexInfo indexInfo = dictionaryInfo.indexInfos.get(i); - final View button = application.createButton(parent.getContext(), - dictionaryInfo, indexInfo); + final View button = IsoUtils.INSTANCE.createButton(parent.getContext(), + indexInfo, application.languageButtonPixels); final IntentLauncher intentLauncher = new IntentLauncher(parent.getContext(), getLaunchIntent(getApplicationContext(), - application.getPath(dictionaryInfo.uncompressedFilename), - indexInfo.shortName, searchView.getQuery().toString())) { + application.getPath(dictionaryInfo.uncompressedFilename), + indexInfo.shortName, searchView.getQuery().toString())) { @Override protected void onGo() { dialog.dismiss(); @@ -726,15 +969,19 @@ public class DictionaryActivity extends ActionBarActivity { } }; button.setOnClickListener(intentLauncher); + if (i == indexIndex && dictFile != null && + dictFile.getName().equals(dictionaryInfo.uncompressedFilename)) { + button.setPressed(true); + } result.addView(button); } final TextView nameView = new TextView(parent.getContext()); final String name = application - .getDictionaryName(dictionaryInfo.uncompressedFilename); + .getDictionaryName(dictionaryInfo.uncompressedFilename); nameView.setText(name); final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); layoutParams.width = 0; layoutParams.weight = 1.0f; nameView.setLayoutParams(layoutParams); @@ -761,7 +1008,7 @@ public class DictionaryActivity extends ActionBarActivity { dialog.show(); } - void onUpDownButton(final boolean up) { + private void onUpDownButton(final boolean up) { if (isFiltered()) { return; } @@ -786,7 +1033,7 @@ public class DictionaryActivity extends ActionBarActivity { defocusSearchText(); } - void onRandomWordButton() { + private void onRandomWordButton() { int destIndexEntry = rand.nextInt(index.sortedIndexEntries.size()); final Index.IndexEntry dest = index.sortedIndexEntries.get(destIndexEntry); setSearchText(dest.token, false); @@ -798,8 +1045,6 @@ public class DictionaryActivity extends ActionBarActivity { // Options Menu // -------------------------------------------------------------------------- - final Random random = new Random(); - @Override public boolean onCreateOptionsMenu(final Menu menu) { @@ -807,7 +1052,7 @@ public class DictionaryActivity extends ActionBarActivity { .getBoolean(getString(R.string.showPrevNextButtonsKey), true)) { // Next word. nextWordMenuItem = menu.add(getString(R.string.nextWord)) - .setIcon(R.drawable.arrow_down_float); + .setIcon(R.drawable.arrow_down_float); MenuItemCompat.setShowAsAction(nextWordMenuItem, MenuItem.SHOW_AS_ACTION_IF_ROOM); nextWordMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override @@ -819,7 +1064,7 @@ public class DictionaryActivity extends ActionBarActivity { // Previous word. previousWordMenuItem = menu.add(getString(R.string.previousWord)) - .setIcon(R.drawable.arrow_up_float); + .setIcon(R.drawable.arrow_up_float); MenuItemCompat.setShowAsAction(previousWordMenuItem, MenuItem.SHOW_AS_ACTION_IF_ROOM); previousWordMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override @@ -830,7 +1075,7 @@ public class DictionaryActivity extends ActionBarActivity { }); } - randomWordMenuItem = menu.add(getString(R.string.randomWord)); + final MenuItem randomWordMenuItem = menu.add(getString(R.string.randomWord)); randomWordMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { @@ -839,8 +1084,6 @@ public class DictionaryActivity extends ActionBarActivity { } }); - application.onCreateGlobalOptionsMenu(this, menu); - { final MenuItem dictionaryManager = menu.add(getString(R.string.dictionaryManager)); MenuItemCompat.setShowAsAction(dictionaryManager, MenuItem.SHOW_AS_ACTION_NEVER); @@ -861,38 +1104,42 @@ public class DictionaryActivity extends ActionBarActivity { final Context context = getListView().getContext(); final Dialog dialog = new Dialog(context); dialog.setContentView(R.layout.about_dictionary_dialog); - final TextView textView = (TextView) dialog.findViewById(R.id.text); + final TextView textView = dialog.findViewById(R.id.text); - final String name = application.getDictionaryName(dictFile.getName()); - dialog.setTitle(name); + dialog.setTitle(dictFileTitleName); final StringBuilder builder = new StringBuilder(); final DictionaryInfo dictionaryInfo = dictionary.getDictionaryInfo(); - dictionaryInfo.uncompressedBytes = dictFile.length(); if (dictionaryInfo != null) { + try { + dictionaryInfo.uncompressedBytes = dictRaf.size(); + } catch (IOException e) { + } builder.append(dictionaryInfo.dictInfo).append("\n\n"); - builder.append(getString(R.string.dictionaryPath, dictFile.getPath())) - .append("\n"); + if (dictFile != null) { + builder.append(getString(R.string.dictionaryPath, dictFile.getPath())) + .append("\n"); + } builder.append( - getString(R.string.dictionarySize, dictionaryInfo.uncompressedBytes)) - .append("\n"); + getString(R.string.dictionarySize, dictionaryInfo.uncompressedBytes)) + .append("\n"); builder.append( - getString(R.string.dictionaryCreationTime, - dictionaryInfo.creationMillis)).append("\n"); + getString(R.string.dictionaryCreationTime, + dictionaryInfo.creationMillis)).append("\n"); for (final IndexInfo indexInfo : dictionaryInfo.indexInfos) { builder.append("\n"); builder.append(getString(R.string.indexName, indexInfo.shortName)) - .append("\n"); + .append("\n"); builder.append( - getString(R.string.mainTokenCount, indexInfo.mainTokenCount)) - .append("\n"); + getString(R.string.mainTokenCount, indexInfo.mainTokenCount)) + .append("\n"); } builder.append("\n"); builder.append(getString(R.string.sources)).append("\n"); for (final EntrySource source : dictionary.sources) { builder.append( - getString(R.string.sourceInfo, source.getName(), - source.getNumEntries())).append("\n"); + getString(R.string.sourceInfo, source.getName(), + source.getNumEntries())).append("\n"); } } textView.setText(builder.toString()); @@ -907,6 +1154,8 @@ public class DictionaryActivity extends ActionBarActivity { }); } + DictionaryApplication.onCreateGlobalOptionsMenu(this, menu); + return true; } @@ -916,18 +1165,31 @@ public class DictionaryActivity extends ActionBarActivity { @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - AdapterContextMenuInfo adapterContextMenuInfo = (AdapterContextMenuInfo) menuInfo; + final AdapterContextMenuInfo adapterContextMenuInfo = (AdapterContextMenuInfo) menuInfo; final RowBase row = (RowBase) getListAdapter().getItem(adapterContextMenuInfo.position); + if (clickOpensContextMenu && (row instanceof HtmlEntry.Row || + (row instanceof TokenRow && ((TokenRow)row).getIndexEntry().htmlEntries.size() > 0))) { + final List html = row instanceof TokenRow ? ((TokenRow)row).getIndexEntry().htmlEntries : Collections.singletonList(((HtmlEntry.Row)row).getEntry()); + final String highlight = row instanceof HtmlEntry.Row ? row.getTokenRow(true).getToken() : null; + final MenuItem open = menu.add("Open"); + open.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + showHtml(html, highlight); + return false; + } + }); + } + final android.view.MenuItem addToWordlist = menu.add(getString(R.string.addToWordList, wordList.getName())); addToWordlist - .setOnMenuItemClickListener(new android.view.MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(android.view.MenuItem item) { - onAppendToWordList(row); - return false; - } - }); + .setOnMenuItemClickListener(new android.view.MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(android.view.MenuItem item) { + onAppendToWordList(row); + return false; + } + }); final android.view.MenuItem share = menu.add("Share"); share.setOnMenuItemClickListener(new android.view.MenuItem.OnMenuItemClickListener() { @@ -935,9 +1197,9 @@ public class DictionaryActivity extends ActionBarActivity { Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND); shareIntent.setType("text/plain"); shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, row.getTokenRow(true) - .getToken()); + .getToken()); shareIntent.putExtra(android.content.Intent.EXTRA_TEXT, - row.getRawText(saveOnlyFirstSubentry)); + row.getRawText(saveOnlyFirstSubentry)); startActivity(shareIntent); return false; } @@ -954,26 +1216,59 @@ public class DictionaryActivity extends ActionBarActivity { if (selectedSpannableText != null) { final String selectedText = selectedSpannableText; final android.view.MenuItem searchForSelection = menu.add(getString( - R.string.searchForSelection, - selectedSpannableText)); + R.string.searchForSelection, + selectedSpannableText)); searchForSelection - .setOnMenuItemClickListener(new android.view.MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(android.view.MenuItem item) { - jumpToTextFromHyperLink(selectedText, selectedSpannableIndex); - return false; - } - }); + .setOnMenuItemClickListener(new android.view.MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(android.view.MenuItem item) { + jumpToTextFromHyperLink(selectedText, selectedSpannableIndex); + return false; + } + }); // Rats, this won't be shown: //searchForSelection.setIcon(R.drawable.abs__ic_search); } - if (row instanceof TokenRow && ttsReady) { + if ((row instanceof TokenRow || selectedSpannableText != null) && ttsReady) { final android.view.MenuItem speak = menu.add(R.string.speak); + final String textToSpeak = row instanceof TokenRow ? ((TokenRow) row).getToken() : selectedSpannableText; + updateTTSLanguage(row instanceof TokenRow ? indexIndex : selectedSpannableIndex); speak.setOnMenuItemClickListener(new android.view.MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(android.view.MenuItem item) { - textToSpeech.speak(((TokenRow) row).getToken(), TextToSpeech.QUEUE_FLUSH, - new HashMap()); + textToSpeech.speak(textToSpeak, TextToSpeech.QUEUE_FLUSH, + new HashMap()); + return false; + } + }); + } + if (row instanceof PairEntry.Row && ttsReady) { + final List pairs = ((PairEntry.Row)row).getEntry().pairs; + final MenuItem speakLeft = menu.add(R.string.speak_left); + speakLeft.setOnMenuItemClickListener(new android.view.MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(android.view.MenuItem item) { + int idx = index.swapPairEntries ? 1 : 0; + updateTTSLanguage(idx); + String text = ""; + for (Pair p : pairs) text += p.get(idx); + text = text.replaceAll("\\{[^{}]*\\}", "").replace("{", "").replace("}", ""); + textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, + new HashMap()); + return false; + } + }); + final MenuItem speakRight = menu.add(R.string.speak_right); + speakRight.setOnMenuItemClickListener(new android.view.MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(android.view.MenuItem item) { + int idx = index.swapPairEntries ? 0 : 1; + updateTTSLanguage(idx); + String text = ""; + for (Pair p : pairs) text += p.get(idx); + text = text.replaceAll("\\{[^{}]*\\}", "").replace("{", "").replace("}", ""); + textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, + new HashMap()); return false; } }); @@ -981,8 +1276,9 @@ public class DictionaryActivity extends ActionBarActivity { } private void jumpToTextFromHyperLink( - final String selectedText, final int defaultIndexToUse) { + final String selectedText, final int defaultIndexToUse) { int indexToUse = -1; + int numFound = 0; for (int i = 0; i < dictionary.indices.size(); ++i) { final Index index = dictionary.indices.get(i); if (indexPrepFinished) { @@ -990,17 +1286,17 @@ public class DictionaryActivity extends ActionBarActivity { final IndexEntry indexEntry = index.findExact(selectedText); if (indexEntry != null) { final TokenRow tokenRow = index.rows.get(indexEntry.startRow) - .getTokenRow(false); + .getTokenRow(false); if (tokenRow != null && tokenRow.hasMainEntry) { indexToUse = i; - break; + ++numFound; } } } else { Log.w(LOG, "Skipping findExact on index " + index.shortName); } } - if (indexToUse == -1) { + if (numFound != 1) { indexToUse = defaultIndexToUse; } // Without this extra delay, the call to jumpToRow that this @@ -1009,7 +1305,8 @@ public class DictionaryActivity extends ActionBarActivity { getListView().postDelayed(new Runnable() { @Override public void run() { - setIndexAndSearchText(actualIndexToUse, selectedText); + setIndexAndSearchText(actualIndexToUse, selectedText, true); + addToSearchHistory(selectedText); } }, 100); } @@ -1018,26 +1315,31 @@ public class DictionaryActivity extends ActionBarActivity { * Called when user clicks outside of search text, so that they can start * typing again immediately. */ - void defocusSearchText() { + private void defocusSearchText() { // Log.d(LOG, "defocusSearchText"); // Request focus so that if we start typing again, it clears the text // input. getListView().requestFocus(); // Visual indication that a new keystroke will clear the search text. - // Doesn't seem to work unless earchText has focus. + // Doesn't seem to work unless searchText has focus. // searchView.selectAll(); } - protected void onListItemClick(ListView l, View v, int row, long id) { + private void onListItemClick(ListView l, View v, int rowIdx, long id) { defocusSearchText(); if (clickOpensContextMenu && dictRaf != null) { openContextMenu(v); + } else { + final RowBase row = (RowBase)getListAdapter().getItem(rowIdx); + if (!(row instanceof PairEntry.Row)) { + v.performClick(); + } } } @SuppressLint("SimpleDateFormat") - void onAppendToWordList(final RowBase row) { + private void onAppendToWordList(final RowBase row) { defocusSearchText(); final StringBuilder rawText = new StringBuilder(); @@ -1050,19 +1352,19 @@ public class DictionaryActivity extends ActionBarActivity { try { wordList.getParentFile().mkdirs(); final PrintWriter out = new PrintWriter(new FileWriter(wordList, true)); - out.println(rawText.toString()); + out.println(rawText); out.close(); } catch (Exception e) { Log.e(LOG, "Unable to append to " + wordList.getAbsolutePath(), e); - Toast.makeText(this, - getString(R.string.failedAddingToWordList, wordList.getAbsolutePath()), - Toast.LENGTH_LONG).show(); + if (!isFinishing()) + Toast.makeText(this, + getString(R.string.failedAddingToWordList, wordList.getAbsolutePath()), + Toast.LENGTH_LONG).show(); } - return; } @SuppressWarnings("deprecation") - void onCopy(final RowBase row) { + private void onCopy(final RowBase row) { defocusSearchText(); Log.d(LOG, "Copy, row=" + row); @@ -1091,14 +1393,17 @@ public class DictionaryActivity extends ActionBarActivity { if (keyCode == KeyEvent.KEYCODE_ENTER) { Log.d(LOG, "Trying to hide soft keyboard."); final InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - inputManager.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), - InputMethodManager.HIDE_NOT_ALWAYS); + View focus = getCurrentFocus(); + if (inputManager != null && focus != null) { + inputManager.hideSoftInputFromWindow(focus.getWindowToken(), + InputMethodManager.HIDE_NOT_ALWAYS); + } return true; } return super.onKeyDown(keyCode, event); } - private void setIndexAndSearchText(int newIndex, String newSearchText) { + private void setIndexAndSearchText(int newIndex, String newSearchText, boolean hideKeyboard) { Log.d(LOG, "Changing index to: " + newIndex); if (newIndex == -1) { Log.e(LOG, "Invalid index."); @@ -1110,45 +1415,31 @@ public class DictionaryActivity extends ActionBarActivity { indexAdapter = new IndexAdapter(index); setListAdapter(indexAdapter); Log.d(LOG, "changingIndex, newLang=" + index.longName); - setDictionaryPrefs(this, dictFile, index.shortName, searchView.getQuery().toString()); + setDictionaryPrefs(this, dictFile, index.shortName); updateLangButton(); } - setSearchText(newSearchText, true); + setSearchText(newSearchText, true, hideKeyboard); } - private void setSearchText(final String text, final boolean triggerSearch) { + private void setSearchText(final String text, final boolean triggerSearch, boolean hideKeyboard) { Log.d(LOG, "setSearchText, text=" + text + ", triggerSearch=" + triggerSearch); // Disable the listener, because sometimes it doesn't work. searchView.setOnQueryTextListener(null); searchView.setQuery(text, false); - moveCursorToRight(); searchView.setOnQueryTextListener(onQueryTextListener); - // Hide search icon once text is entered - searchView.setIconifiedByDefault(text.length() > 0); - searchView.setIconified(false); - if (triggerSearch) { - onQueryTextListener.onQueryTextChange(text); + onSearchTextChange(text); + } + + // We don't want to show virtual keyboard when we're changing searchView text programatically: + if (hideKeyboard) { + hideKeyboard(); } } - // private long cursorDelayMillis = 100; - private void moveCursorToRight() { - // if (searchText.getLayout() != null) { - // cursorDelayMillis = 100; - // // Surprising, but this can crash when you rotate... - // Selection.moveToRightEdge(searchView.getQuery(), - // searchText.getLayout()); - // } else { - // uiHandler.postDelayed(new Runnable() { - // @Override - // public void run() { - // moveCursorToRight(); - // } - // }, cursorDelayMillis); - // cursorDelayMillis = Math.min(10 * 1000, 2 * cursorDelayMillis); - // } + private void setSearchText(final String text, final boolean triggerSearch) { + setSearchText(text, triggerSearch, true); } // -------------------------------------------------------------------------- @@ -1169,7 +1460,9 @@ public class DictionaryActivity extends ActionBarActivity { Log.d(LOG, "searchFinished: " + searchOperation + ", searchResult=" + searchResult); currentSearchOperation = null; - uiHandler.postDelayed(new Runnable() { + // Note: it's important to post to the ListView, otherwise + // the jumpToRow will randomly not work. + getListView().post(new Runnable() { @Override public void run() { if (currentSearchOperation == null) { @@ -1188,17 +1481,17 @@ public class DictionaryActivity extends ActionBarActivity { Log.d(LOG, "More coming, waiting for currentSearchOperation."); } } - }, 20); + }); } - private final void jumpToRow(final int row) { + private void jumpToRow(final int row) { Log.d(LOG, "jumpToRow: " + row + ", refocusSearchText=" + false); // getListView().requestFocusFromTouch(); getListView().setSelectionFromTop(row, 0); getListView().setSelected(true); } - static final Pattern WHITESPACE = Pattern.compile("\\s+"); + private static final Pattern WHITESPACE = Pattern.compile("\\s+"); final class SearchOperation implements Runnable { @@ -1224,7 +1517,7 @@ public class DictionaryActivity extends ActionBarActivity { } public String toString() { - return String.format("SearchOperation(%s,%s)", searchText, interrupted.toString()); + return String.format("SearchOperation(%s,%s)", searchText, interrupted); } @Override @@ -1237,12 +1530,12 @@ public class DictionaryActivity extends ActionBarActivity { } else { searchTokens = Arrays.asList(searchTokenArray); multiWordSearchResult = index.multiWordSearch(searchText, searchTokens, - interrupted); + interrupted); } Log.d(LOG, - "searchText=" + searchText + ", searchDuration=" - + (System.currentTimeMillis() - searchStartMillis) - + ", interrupted=" + interrupted.get()); + "searchText=" + searchText + ", searchDuration=" + + (System.currentTimeMillis() - searchStartMillis) + + ", interrupted=" + interrupted.get()); if (!interrupted.get()) { uiHandler.post(new Runnable() { @Override @@ -1254,7 +1547,7 @@ public class DictionaryActivity extends ActionBarActivity { Log.d(LOG, "interrupted, skipping searchFinished."); } } catch (Exception e) { - Log.e(LOG, "Failure during search (can happen during Activity close."); + Log.e(LOG, "Failure during search (can happen during Activity close): " + e.getMessage()); } finally { synchronized (this) { done = true; @@ -1268,11 +1561,21 @@ public class DictionaryActivity extends ActionBarActivity { // IndexAdapter // -------------------------------------------------------------------------- - static ViewGroup.LayoutParams WEIGHT_1 = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f); - - static ViewGroup.LayoutParams WEIGHT_0 = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT, 0.0f); + private void showHtml(final List htmlEntries, final String htmlTextToHighlight) { + String html = HtmlEntry.htmlBody(htmlEntries, index.shortName); + String style = ""; + if (typeface == Typeface.SERIF) { style = "font-family: serif;"; } + else if (typeface == Typeface.SANS_SERIF) { style = "font-family: sans-serif;"; } + else if (typeface == Typeface.MONOSPACE) { style = "font-family: monospace;"; } + if (application.getSelectedTheme() == DictionaryApplication.Theme.DEFAULT) + style += "background-color: black; color: white;"; + // Log.d(LOG, "html=" + html); + startActivityForResult( + HtmlDisplayActivity.getHtmlIntent(getApplicationContext(), String.format( + "%s", style, html), + htmlTextToHighlight, false), + 0); + } final class IndexAdapter extends BaseAdapter { @@ -1300,13 +1603,23 @@ public class DictionaryActivity extends ActionBarActivity { IndexAdapter(final Index index, final List rows, final List toHighlight) { this.index = index; this.rows = rows; - this.toHighlight = new LinkedHashSet(toHighlight); + this.toHighlight = new LinkedHashSet<>(toHighlight); getMetrics(); } private void getMetrics() { + float scale = 1; // Get the screen's density scale - final float scale = getResources().getDisplayMetrics().density; + // The previous method getResources().getDisplayMetrics() + // used to occasionally trigger a null pointer exception, + // so try this instead. + // As it still crashes, add a fallback + try { + DisplayMetrics dm = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(dm); + scale = dm.density; + } catch (NullPointerException ignored) + {} // Convert the dps to pixels, based on density scale mPaddingDefault = (int) (PADDING_DEFAULT_DP * scale + 0.5f); mPaddingLarge = (int) (PADDING_LARGE_DP * scale + 0.5f); @@ -1328,85 +1641,89 @@ public class DictionaryActivity extends ActionBarActivity { } @Override - public TableLayout getView(int position, View convertView, ViewGroup parent) { - final TableLayout result; - if (convertView instanceof TableLayout) { - result = (TableLayout) convertView; - result.removeAllViews(); + public int getViewTypeCount() { + return 5; + } + + @Override + public int getItemViewType(int position) { + final RowBase row = getItem(position); + if (row instanceof PairEntry.Row) { + final PairEntry entry = ((PairEntry.Row)row).getEntry(); + final int rowCount = entry.pairs.size(); + return rowCount > 1 ? 1 : 0; + } else if (row instanceof TokenRow) { + final IndexEntry indexEntry = ((TokenRow)row).getIndexEntry(); + return indexEntry.htmlEntries.isEmpty() ? 2 : 3; + } else if (row instanceof HtmlEntry.Row) { + return 4; } else { - result = new TableLayout(parent.getContext()); + throw new IllegalArgumentException("Unsupported Row type: " + row.getClass()); } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { final RowBase row = getItem(position); if (row instanceof PairEntry.Row) { - return getView(position, (PairEntry.Row) row, parent, result); + return getView(position, (PairEntry.Row) row, parent, (TableLayout)convertView); } else if (row instanceof TokenRow) { - return getView((TokenRow) row, parent, result); + return getView((TokenRow) row, parent, (TextView)convertView); } else if (row instanceof HtmlEntry.Row) { - return getView((HtmlEntry.Row) row, parent, result); + return getView((HtmlEntry.Row) row, parent, (TextView)convertView); } else { throw new IllegalArgumentException("Unsupported Row type: " + row.getClass()); } } + private void addBoldSpans(String token, String col1Text, Spannable col1Spannable) { + int startPos = 0; + while ((startPos = col1Text.indexOf(token, startPos)) != -1) { + col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos, startPos + + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + startPos += token.length(); + } + } + private TableLayout getView(final int position, PairEntry.Row row, ViewGroup parent, - final TableLayout result) { + TableLayout result) { + final Context context = parent.getContext(); final PairEntry entry = row.getEntry(); final int rowCount = entry.pairs.size(); + if (result == null) { + result = new TableLayout(context); + result.setStretchAllColumns(true); + // Because we have a Button inside a ListView row: + // http://groups.google.com/group/android-developers/browse_thread/thread/3d96af1530a7d62a?pli=1 + result.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + result.setClickable(true); + result.setFocusable(false); + result.setLongClickable(true); +// result.setBackgroundResource(android.R.drawable.menuitem_background); - final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams(); - layoutParams.weight = 0.5f; - layoutParams.leftMargin = mPaddingLarge; + result.setBackgroundResource(theme.normalRowBg); + } else if (result.getChildCount() > rowCount) { + result.removeViews(rowCount, result.getChildCount() - rowCount); + } + + for (int r = result.getChildCount(); r < rowCount; ++r) { + final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT); + layoutParams.leftMargin = mPaddingLarge; - for (int r = 0; r < rowCount; ++r) { final TableRow tableRow = new TableRow(result.getContext()); final TextView col1 = new TextView(tableRow.getContext()); final TextView col2 = new TextView(tableRow.getContext()); - - // Set the columns in the table. - if (r > 0) { - final TextView bullet = new TextView(tableRow.getContext()); - bullet.setText(" • "); - tableRow.addView(bullet); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + col1.setTextIsSelectable(true); + col2.setTextIsSelectable(true); } - tableRow.addView(col1, layoutParams); - final TextView margin = new TextView(tableRow.getContext()); - margin.setText(" "); - tableRow.addView(margin); - if (r > 0) { - final TextView bullet = new TextView(tableRow.getContext()); - bullet.setText(" • "); - tableRow.addView(bullet); - } - tableRow.addView(col2, layoutParams); + col1.setTextColor(textColorFg); + col2.setTextColor(textColorFg); + col1.setWidth(1); col2.setWidth(1); - // Set what's in the columns. - - final Pair pair = entry.pairs.get(r); - final String col1Text = index.swapPairEntries ? pair.lang2 : pair.lang1; - final String col2Text = index.swapPairEntries ? pair.lang1 : pair.lang2; - - col1.setText(col1Text, TextView.BufferType.SPANNABLE); - col2.setText(col2Text, TextView.BufferType.SPANNABLE); - - // Bold the token instances in col1. - final Set toBold = toHighlight != null ? this.toHighlight : Collections - .singleton(row.getTokenRow(true).getToken()); - final Spannable col1Spannable = (Spannable) col1.getText(); - for (final String token : toBold) { - int startPos = 0; - while ((startPos = col1Text.indexOf(token, startPos)) != -1) { - col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos, startPos - + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - startPos += token.length(); - } - } - - createTokenLinkSpans(col1, col1Spannable, col1Text); - createTokenLinkSpans(col2, (Spannable) col2.getText(), col2Text); - col1.setTypeface(typeface); col2.setTypeface(typeface); col1.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSizeSp); @@ -1421,18 +1738,59 @@ public class DictionaryActivity extends ActionBarActivity { col2.setOnLongClickListener(textViewLongClickListenerIndex1); } + // Set the columns in the table. + if (r == 0) { + tableRow.addView(col1, layoutParams); + tableRow.addView(col2, layoutParams); + } else { + for (int i = 0; i < 2; i++) { + final TextView bullet = new TextView(tableRow.getContext()); + bullet.setText(" • "); + LinearLayout wrapped = new LinearLayout(context); + wrapped.setOrientation(LinearLayout.HORIZONTAL); + LinearLayout.LayoutParams p1 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, 0); + wrapped.addView(bullet, p1); + LinearLayout.LayoutParams p2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT, 1); + wrapped.addView(i == 0 ? col1 : col2, p2); + tableRow.addView(wrapped, layoutParams); + } + } + result.addView(tableRow); } - // Because we have a Button inside a ListView row: - // http://groups.google.com/group/android-developers/browse_thread/thread/3d96af1530a7d62a?pli=1 - result.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - result.setClickable(true); - result.setFocusable(true); - result.setLongClickable(true); -// result.setBackgroundResource(android.R.drawable.menuitem_background); - - result.setBackgroundResource(theme.normalRowBg); + for (int r = 0; r < rowCount; ++r) { + final TableRow tableRow = (TableRow)result.getChildAt(r); + View left = tableRow.getChildAt(0); + View right = tableRow.getChildAt(1); + if (r > 0) { + left = ((ViewGroup)left).getChildAt(1); + right = ((ViewGroup)right).getChildAt(1); + } + final TextView col1 = (TextView)left; + final TextView col2 = (TextView)right; + + // Set what's in the columns. + final Pair pair = entry.pairs.get(r); + final String col1Text = index.swapPairEntries ? pair.lang2 : pair.lang1; + final String col2Text = index.swapPairEntries ? pair.lang1 : pair.lang2; + final Spannable col1Spannable = new SpannableString(col1Text); + final Spannable col2Spannable = new SpannableString(col2Text); + + // Bold the token instances in col1. + if (toHighlight != null) { + for (final String token : toHighlight) { + addBoldSpans(token, col1Text, col1Spannable); + } + } else + addBoldSpans(row.getTokenRow(true).getToken(), col1Text, col1Spannable); + + createTokenLinkSpans(col1, col1Spannable, col1Text); + createTokenLinkSpans(col2, col2Spannable, col2Text); + + col1.setText(col1Spannable); + col2.setText(col2Spannable); + } result.setOnClickListener(new TextView.OnClickListener() { @Override @@ -1444,44 +1802,41 @@ public class DictionaryActivity extends ActionBarActivity { return result; } - private TableLayout getPossibleLinkToHtmlEntryView(final boolean isTokenRow, + private TextView getPossibleLinkToHtmlEntryView(final boolean isTokenRow, final String text, final boolean hasMainEntry, final List htmlEntries, - final String htmlTextToHighlight, ViewGroup parent, final TableLayout result) { + final String htmlTextToHighlight, ViewGroup parent, TextView textView) { final Context context = parent.getContext(); - - final TableRow tableRow = new TableRow(result.getContext()); - tableRow.setBackgroundResource(hasMainEntry ? theme.tokenRowMainBg - : theme.tokenRowOtherBg); - if (isTokenRow) { - tableRow.setPadding(mPaddingDefault, mPaddingDefault, mPaddingDefault, 0); - } else { - tableRow.setPadding(mPaddingLarge, mPaddingDefault, mPaddingDefault, 0); + if (textView == null) { + textView = new TextView(context); + // set up things invariant across one ItemViewType + // ItemViewTypes handled here are: + // 2: isTokenRow == true, htmlEntries.isEmpty() == true + // 3: isTokenRow == true, htmlEntries.isEmpty() == false + // 4: isTokenRow == false, htmlEntries.isEmpty() == false + textView.setPadding(isTokenRow ? mPaddingDefault : mPaddingLarge, mPaddingDefault, mPaddingDefault, 0); + textView.setOnLongClickListener(indexIndex > 0 ? textViewLongClickListenerIndex1 : textViewLongClickListenerIndex0); + textView.setLongClickable(true); + + textView.setTypeface(typeface); + if (isTokenRow) { + textView.setTextAppearance(context, theme.tokenRowFg); + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 4 * fontSizeSp / 3); + } else { + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSizeSp); + } + textView.setTextColor(textColorFg); + if (!htmlEntries.isEmpty()) { + textView.setClickable(true); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + } } - result.addView(tableRow); - // Make it so we can long-click on these token rows, too: - final TextView textView = new TextView(context); - textView.setText(text, BufferType.SPANNABLE); - createTokenLinkSpans(textView, (Spannable) textView.getText(), text); - final TextViewLongClickListener textViewLongClickListenerIndex0 = new TextViewLongClickListener( - 0); - textView.setOnLongClickListener(textViewLongClickListenerIndex0); - result.setLongClickable(true); - - // Doesn't work: - // textView.setTextColor(android.R.color.secondary_text_light); - textView.setTypeface(typeface); - TableRow.LayoutParams lp = new TableRow.LayoutParams(0); - if (isTokenRow) { - textView.setTextAppearance(context, theme.tokenRowFg); - textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 4 * fontSizeSp / 3); - } else { - textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSizeSp); - } - lp.weight = 1.0f; + textView.setBackgroundResource(hasMainEntry ? theme.tokenRowMainBg + : theme.tokenRowOtherBg); - textView.setLayoutParams(lp); - tableRow.addView(textView); + // Make it so we can long-click on these token rows, too: + final Spannable textSpannable = new SpannableString(text); + createTokenLinkSpans(textView, textSpannable, text); if (!htmlEntries.isEmpty()) { final ClickableSpan clickableSpan = new ClickableSpan() { @@ -1489,62 +1844,54 @@ public class DictionaryActivity extends ActionBarActivity { public void onClick(View widget) { } }; - ((Spannable) textView.getText()).setSpan(clickableSpan, 0, text.length(), + textSpannable.setSpan(clickableSpan, 0, text.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); - result.setClickable(true); - textView.setClickable(true); - textView.setMovementMethod(LinkMovementMethod.getInstance()); textView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - String html = HtmlEntry.htmlBody(htmlEntries, index.shortName); - // Log.d(LOG, "html=" + html); - startActivityForResult( - HtmlDisplayActivity.getHtmlIntent(getApplicationContext(), String.format( - "%s", html), - htmlTextToHighlight, false), - 0); + showHtml(htmlEntries, htmlTextToHighlight); } }); } - return result; + textView.setText(textSpannable); + return textView; } - private TableLayout getView(TokenRow row, ViewGroup parent, final TableLayout result) { + private TextView getView(TokenRow row, ViewGroup parent, final TextView result) { final IndexEntry indexEntry = row.getIndexEntry(); return getPossibleLinkToHtmlEntryView(true, indexEntry.token, row.hasMainEntry, - indexEntry.htmlEntries, null, parent, result); + indexEntry.htmlEntries, null, parent, result); } - private TableLayout getView(HtmlEntry.Row row, ViewGroup parent, final TableLayout result) { + private TextView getView(HtmlEntry.Row row, ViewGroup parent, final TextView result) { final HtmlEntry htmlEntry = row.getEntry(); final TokenRow tokenRow = row.getTokenRow(true); return getPossibleLinkToHtmlEntryView(false, - getString(R.string.seeAlso, htmlEntry.title, htmlEntry.entrySource.getName()), - false, Collections.singletonList(htmlEntry), tokenRow.getToken(), parent, - result); + getString(R.string.seeAlso, htmlEntry.title, htmlEntry.entrySource.getName()), + false, Collections.singletonList(htmlEntry), tokenRow.getToken(), parent, + result); } } - static final Pattern CHAR_DASH = Pattern.compile("['\\p{L}\\p{M}\\p{N}]+"); + private static final Pattern CHAR_DASH = Pattern.compile("['\\p{L}\\p{M}\\p{N}]+"); private void createTokenLinkSpans(final TextView textView, final Spannable spannable, - final String text) { + final String text) { // Saw from the source code that LinkMovementMethod sets the selection! // http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.1_r1/android/text/method/LinkMovementMethod.java#LinkMovementMethod textView.setMovementMethod(LinkMovementMethod.getInstance()); final Matcher matcher = CHAR_DASH.matcher(text); while (matcher.find()) { - spannable.setSpan(new NonLinkClickableSpan(textColorFg), matcher.start(), - matcher.end(), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + spannable.setSpan(new NonLinkClickableSpan(), matcher.start(), + matcher.end(), + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } } - String selectedSpannableText = null; + private String selectedSpannableText = null; - int selectedSpannableIndex = -1; + private int selectedSpannableIndex = -1; @Override public boolean onTouchEvent(MotionEvent event) { @@ -1573,22 +1920,22 @@ public class DictionaryActivity extends ActionBarActivity { } } - final TextViewLongClickListener textViewLongClickListenerIndex0 = new TextViewLongClickListener( - 0); + private final TextViewLongClickListener textViewLongClickListenerIndex0 = new TextViewLongClickListener( + 0); - final TextViewLongClickListener textViewLongClickListenerIndex1 = new TextViewLongClickListener( - 1); + private final TextViewLongClickListener textViewLongClickListenerIndex1 = new TextViewLongClickListener( + 1); // -------------------------------------------------------------------------- // SearchText // -------------------------------------------------------------------------- - void onSearchTextChange(final String text) { + private void onSearchTextChange(final String text) { if ("thadolina".equals(text)) { final Dialog dialog = new Dialog(getListView().getContext()); dialog.setContentView(R.layout.thadolina_dialog); dialog.setTitle("Ti amo, amore mio!"); - final ImageView imageView = (ImageView) dialog.findViewById(R.id.thadolina_image); + final ImageView imageView = dialog.findViewById(R.id.thadolina_image); imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -1604,10 +1951,6 @@ public class DictionaryActivity extends ActionBarActivity { return; } - // Hide search icon once text is entered - searchView.setIconifiedByDefault(text.length() > 0); - searchView.setIconified(false); - // if (!searchView.hasFocus()) { // Log.d(LOG, "searchText changed without focus, doing nothing."); // return; @@ -1619,17 +1962,20 @@ public class DictionaryActivity extends ActionBarActivity { } currentSearchOperation = new SearchOperation(text, index); searchExecutor.execute(currentSearchOperation); + ((FloatingActionButton)findViewById(R.id.floatSearchButton)).setImageResource(text.length() > 0 ? R.drawable.ic_clear_black_24dp : R.drawable.ic_search_black_24dp); + searchView.getSuggestionsAdapter().swapCursor(text.isEmpty() ? searchHistoryCursor : null); + searchView.getSuggestionsAdapter().notifyDataSetChanged(); } // -------------------------------------------------------------------------- // Filtered results. // -------------------------------------------------------------------------- - boolean isFiltered() { + private boolean isFiltered() { return rowsToShow != null; } - void setFiltered(final SearchOperation searchOperation) { + private void setFiltered(final SearchOperation searchOperation) { if (nextWordMenuItem != null) { nextWordMenuItem.setEnabled(false); previousWordMenuItem.setEnabled(false); @@ -1638,7 +1984,7 @@ public class DictionaryActivity extends ActionBarActivity { setListAdapter(new IndexAdapter(index, rowsToShow, searchOperation.searchTokens)); } - void clearFiltered() { + private void clearFiltered() { if (nextWordMenuItem != null) { nextWordMenuItem.setEnabled(true); previousWordMenuItem.setEnabled(true);