X-Git-Url: http://gitweb.fperrin.net/?p=Dictionary.git;a=blobdiff_plain;f=src%2Fcom%2Fhughes%2Fandroid%2Fdictionary%2FDictionaryActivity.java;h=e2558a0773605615ff629cb7aab682a9605a2de3;hp=43c9003a123e4eb3387a68eacc7f793c9d2f4c23;hb=c76660b2772122109529d3616289980a7084eeeb;hpb=f5da479c90cbcfc62279da4df6c17fb5c847f202 diff --git a/src/com/hughes/android/dictionary/DictionaryActivity.java b/src/com/hughes/android/dictionary/DictionaryActivity.java index 43c9003..e2558a0 100644 --- a/src/com/hughes/android/dictionary/DictionaryActivity.java +++ b/src/com/hughes/android/dictionary/DictionaryActivity.java @@ -21,24 +21,29 @@ 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; @@ -63,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; @@ -75,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; @@ -84,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; @@ -101,6 +105,7 @@ 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; @@ -110,7 +115,6 @@ 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; @@ -118,44 +122,47 @@ 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; - FileChannel dictRaf = null; - String dictFileTitleName = 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 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() { @@ -173,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; @@ -185,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 @@ -213,6 +222,7 @@ 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) { @@ -229,6 +239,8 @@ public class DictionaryActivity extends ActionBarActivity { 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) { @@ -236,12 +248,44 @@ public class DictionaryActivity extends ActionBarActivity { } dictRaf = null; } - Toast.makeText(this, getString(R.string.invalidDictionary, "", e.getMessage()), - Toast.LENGTH_LONG).show(); + 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()); @@ -275,14 +319,14 @@ public class DictionaryActivity extends ActionBarActivity { 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) @@ -325,8 +369,8 @@ public class DictionaryActivity extends ActionBarActivity { } } - /** - * @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) */ @@ -361,8 +405,8 @@ public class DictionaryActivity extends ActionBarActivity { return; } } - /** - * @author Dominik Köppl If no dictionary is chosen, use the default + /* + @author Dominik Köppl If no dictionary is chosen, use the default * dictionary specified in the preferences If this step does * fail (no default dictionary specified), show a toast and * abort. @@ -411,12 +455,13 @@ public class DictionaryActivity extends ActionBarActivity { } if (dictFilename == null) { - Toast.makeText(this, getString(R.string.no_dict_file), Toast.LENGTH_LONG).show(); + if (!isFinishing()) + Toast.makeText(this, getString(R.string.no_dict_file), Toast.LENGTH_LONG).show(); startActivity(DictionaryManagerActivity.getLaunchIntent(getApplicationContext())); finish(); return; } - if (dictRaf == null && dictFilename != null) + if (dictRaf == null) dictFile = new File(dictFilename); ttsReady = false; @@ -499,25 +544,32 @@ public class DictionaryActivity extends ActionBarActivity { }).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."); @@ -560,6 +612,54 @@ 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); @@ -618,6 +718,7 @@ public class DictionaryActivity extends ActionBarActivity { @Override public boolean onQueryTextSubmit(String query) { Log.d(LOG, "OnQueryTextListener: onQueryTextSubmit: " + searchView.getQuery()); + addToSearchHistory(); hideKeyboard(); return true; } @@ -631,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); @@ -659,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)) { @@ -689,6 +786,13 @@ public class DictionaryActivity extends ActionBarActivity { prefs.commit(); } + @Override + protected void onPause() { + super.onPause(); + addToSearchHistory(); + saveSearchHistory(); + } + @Override protected void onDestroy() { super.onDestroy(); @@ -709,6 +813,9 @@ public class DictionaryActivity extends ActionBarActivity { textToSpeech.shutdown(); textToSpeech = null; + indexAdapter = null; + setListAdapter(null); + try { Log.d(LOG, "Closing RAF."); dictRaf.close(); @@ -733,14 +840,23 @@ 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(); + } + }); } private void hideKeyboard() { @@ -750,7 +866,7 @@ public class DictionaryActivity extends ActionBarActivity { manager.hideSoftInputFromWindow(searchView.getWindowToken(), 0); } - void updateLangButton() { + private void updateLangButton() { final int flagId = IsoUtils.INSTANCE.getFlagIdForIsoCode(index.shortName); if (flagId != 0) { languageButton.setImageResource(flagId); @@ -778,7 +894,8 @@ public class DictionaryActivity extends ActionBarActivity { Log.e(LOG, "TTS not available in this language: ttsResult=" + ttsResult); } } catch (Exception e) { - Toast.makeText(this, getString(R.string.TTSbroken), Toast.LENGTH_LONG).show(); + if (!isFinishing()) + Toast.makeText(this, getString(R.string.TTSbroken), Toast.LENGTH_LONG).show(); } } @@ -787,6 +904,7 @@ public class DictionaryActivity extends ActionBarActivity { searchView.requestFocus(); } if (searchView.getQuery().toString().length() > 0) { + addToSearchHistory(); searchView.setQuery("", false); } showKeyboard(); @@ -806,14 +924,14 @@ public class DictionaryActivity extends ActionBarActivity { 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); @@ -836,10 +954,10 @@ public class DictionaryActivity extends ActionBarActivity { final LinearLayout result = new LinearLayout(parent.getContext()); - for (int i = 0; dictionaryInfo.indexInfos != null && i < dictionaryInfo.indexInfos.size(); ++i) { + for (int i = 0; i < dictionaryInfo.indexInfos.size(); ++i) { final IndexInfo indexInfo = dictionaryInfo.indexInfos.get(i); final View button = IsoUtils.INSTANCE.createButton(parent.getContext(), - dictionaryInfo, indexInfo, application.languageButtonPixels); + indexInfo, application.languageButtonPixels); final IntentLauncher intentLauncher = new IntentLauncher(parent.getContext(), getLaunchIntent(getApplicationContext(), application.getPath(dictionaryInfo.uncompressedFilename), @@ -890,7 +1008,7 @@ public class DictionaryActivity extends ActionBarActivity { dialog.show(); } - void onUpDownButton(final boolean up) { + private void onUpDownButton(final boolean up) { if (isFiltered()) { return; } @@ -915,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); @@ -927,8 +1045,6 @@ public class DictionaryActivity extends ActionBarActivity { // Options Menu // -------------------------------------------------------------------------- - final Random random = new Random(); - @Override public boolean onCreateOptionsMenu(final Menu menu) { @@ -959,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) { @@ -968,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); @@ -990,7 +1104,7 @@ 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); dialog.setTitle(dictFileTitleName); @@ -1040,6 +1154,8 @@ public class DictionaryActivity extends ActionBarActivity { }); } + DictionaryApplication.onCreateGlobalOptionsMenu(this, menu); + return true; } @@ -1055,7 +1171,7 @@ public class DictionaryActivity extends ActionBarActivity { 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 ? ((HtmlEntry.Row)row).getTokenRow(true).getToken() : null; + 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) { @@ -1180,7 +1296,7 @@ public class DictionaryActivity extends ActionBarActivity { Log.w(LOG, "Skipping findExact on index " + index.shortName); } } - if (numFound != 1 || indexToUse == -1) { + if (numFound != 1) { indexToUse = defaultIndexToUse; } // Without this extra delay, the call to jumpToRow that this @@ -1190,6 +1306,7 @@ public class DictionaryActivity extends ActionBarActivity { @Override public void run() { setIndexAndSearchText(actualIndexToUse, selectedText, true); + addToSearchHistory(selectedText); } }, 100); } @@ -1198,7 +1315,7 @@ 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. @@ -1209,7 +1326,7 @@ public class DictionaryActivity extends ActionBarActivity { // searchView.selectAll(); } - protected void onListItemClick(ListView l, View v, int rowIdx, long id) { + private void onListItemClick(ListView l, View v, int rowIdx, long id) { defocusSearchText(); if (clickOpensContextMenu && dictRaf != null) { openContextMenu(v); @@ -1222,7 +1339,7 @@ public class DictionaryActivity extends ActionBarActivity { } @SuppressLint("SimpleDateFormat") - void onAppendToWordList(final RowBase row) { + private void onAppendToWordList(final RowBase row) { defocusSearchText(); final StringBuilder rawText = new StringBuilder(); @@ -1235,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); @@ -1277,7 +1394,7 @@ public class DictionaryActivity extends ActionBarActivity { Log.d(LOG, "Trying to hide soft keyboard."); final InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); View focus = getCurrentFocus(); - if (focus != null) { + if (inputManager != null && focus != null) { inputManager.hideSoftInputFromWindow(focus.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } @@ -1309,7 +1426,6 @@ public class DictionaryActivity extends ActionBarActivity { // Disable the listener, because sometimes it doesn't work. searchView.setOnQueryTextListener(null); searchView.setQuery(text, false); - moveCursorToRight(); searchView.setOnQueryTextListener(onQueryTextListener); if (triggerSearch) { @@ -1326,24 +1442,6 @@ public class DictionaryActivity extends ActionBarActivity { setSearchText(text, triggerSearch, true); } - // 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); - // } - } - // -------------------------------------------------------------------------- // SearchOperation // -------------------------------------------------------------------------- @@ -1362,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) { @@ -1381,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 { @@ -1417,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 @@ -1447,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; @@ -1463,20 +1563,20 @@ public class DictionaryActivity extends ActionBarActivity { 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", html), + "%s", style, html), htmlTextToHighlight, false), 0); } - 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); - final class IndexAdapter extends BaseAdapter { private static final float PADDING_DEFAULT_DP = 8; @@ -1503,7 +1603,7 @@ 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(); } @@ -1518,7 +1618,7 @@ public class DictionaryActivity extends ActionBarActivity { DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); scale = dm.density; - } catch (NullPointerException e) + } catch (NullPointerException ignored) {} // Convert the dps to pixels, based on density scale mPaddingDefault = (int) (PADDING_DEFAULT_DP * scale + 0.5f); @@ -1576,22 +1676,40 @@ public class DictionaryActivity extends ActionBarActivity { } } + 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, 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); - } else { - result.removeAllViews(); + 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); + + result.setBackgroundResource(theme.normalRowBg); + } else if (result.getChildCount() > rowCount) { + result.removeViews(rowCount, result.getChildCount() - rowCount); } - final PairEntry entry = row.getEntry(); - final int rowCount = entry.pairs.size(); - final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams(); - layoutParams.weight = 0.5f; - layoutParams.leftMargin = mPaddingLarge; + 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()); @@ -1600,48 +1718,12 @@ public class DictionaryActivity extends ActionBarActivity { col1.setTextIsSelectable(true); col2.setTextIsSelectable(true); } + col1.setTextColor(textColorFg); + col2.setTextColor(textColorFg); - // Set the columns in the table. - if (r > 0) { - final TextView bullet = new TextView(tableRow.getContext()); - bullet.setText(" •"); - tableRow.addView(bullet); - } - tableRow.addView(col1, layoutParams); - if (r > 0) { - final TextView bullet = new TextView(tableRow.getContext()); - bullet.setText(" •"); - tableRow.addView(bullet); - } - tableRow.addView(col2, layoutParams); 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); @@ -1656,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(false); - result.setLongClickable(true); -// result.setBackgroundResource(android.R.drawable.menuitem_background); + 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; - result.setBackgroundResource(theme.normalRowBg); + // 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 @@ -1694,8 +1817,6 @@ public class DictionaryActivity extends ActionBarActivity { textView.setOnLongClickListener(indexIndex > 0 ? textViewLongClickListenerIndex1 : textViewLongClickListenerIndex0); textView.setLongClickable(true); - // Doesn't work: - // textView.setTextColor(android.R.color.secondary_text_light); textView.setTypeface(typeface); if (isTokenRow) { textView.setTextAppearance(context, theme.tokenRowFg); @@ -1703,6 +1824,7 @@ public class DictionaryActivity extends ActionBarActivity { } else { textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSizeSp); } + textView.setTextColor(textColorFg); if (!htmlEntries.isEmpty()) { textView.setClickable(true); textView.setMovementMethod(LinkMovementMethod.getInstance()); @@ -1713,8 +1835,8 @@ public class DictionaryActivity extends ActionBarActivity { : theme.tokenRowOtherBg); // Make it so we can long-click on these token rows, too: - textView.setText(text, BufferType.SPANNABLE); - createTokenLinkSpans(textView, (Spannable) textView.getText(), text); + final Spannable textSpannable = new SpannableString(text); + createTokenLinkSpans(textView, textSpannable, text); if (!htmlEntries.isEmpty()) { final ClickableSpan clickableSpan = new ClickableSpan() { @@ -1722,7 +1844,7 @@ 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); textView.setOnClickListener(new OnClickListener() { @Override @@ -1731,6 +1853,7 @@ public class DictionaryActivity extends ActionBarActivity { } }); } + textView.setText(textSpannable); return textView; } @@ -1751,7 +1874,7 @@ public class DictionaryActivity extends ActionBarActivity { } - 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) { @@ -1760,15 +1883,15 @@ public class DictionaryActivity extends ActionBarActivity { textView.setMovementMethod(LinkMovementMethod.getInstance()); final Matcher matcher = CHAR_DASH.matcher(text); while (matcher.find()) { - spannable.setSpan(new NonLinkClickableSpan(textColorFg), matcher.start(), + 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) { @@ -1797,22 +1920,22 @@ public class DictionaryActivity extends ActionBarActivity { } } - final TextViewLongClickListener textViewLongClickListenerIndex0 = new TextViewLongClickListener( + private final TextViewLongClickListener textViewLongClickListenerIndex0 = new TextViewLongClickListener( 0); - final TextViewLongClickListener textViewLongClickListenerIndex1 = new TextViewLongClickListener( + 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) { @@ -1840,17 +1963,19 @@ 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); @@ -1859,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);