| 1 | /* This file is part of Aard Dictionary for Android <http://aarddict.org>. |
| 2 | * |
| 3 | * This program is free software: you can redistribute it and/or modify |
| 4 | * it under the terms of the GNU General Public License version 3 |
| 5 | * as published by the Free Software Foundation. |
| 6 | * |
| 7 | * This program is distributed in the hope that it will be useful, |
| 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | * GNU General Public License <http://www.gnu.org/licenses/gpl-3.0.txt> |
| 11 | * for more details. |
| 12 | * |
| 13 | * Copyright (C) 2010 Igor Tkach |
| 14 | */ |
| 15 | |
| 16 | package aarddict.android; |
| 17 | |
| 18 | import java.util.ArrayList; |
| 19 | import java.util.Iterator; |
| 20 | import java.util.List; |
| 21 | import java.util.Timer; |
| 22 | import java.util.TimerTask; |
| 23 | |
| 24 | import aarddict.Entry; |
| 25 | import android.app.AlertDialog; |
| 26 | import android.content.Context; |
| 27 | import android.content.DialogInterface; |
| 28 | import android.content.Intent; |
| 29 | import android.content.DialogInterface.OnClickListener; |
| 30 | import android.content.pm.PackageInfo; |
| 31 | import android.content.pm.PackageManager; |
| 32 | import android.content.pm.PackageManager.NameNotFoundException; |
| 33 | import android.content.res.Resources; |
| 34 | import android.net.Uri; |
| 35 | import android.text.Editable; |
| 36 | import android.text.Html; |
| 37 | import android.text.InputType; |
| 38 | import android.text.TextUtils; |
| 39 | import android.text.TextWatcher; |
| 40 | import android.text.method.LinkMovementMethod; |
| 41 | import android.util.Log; |
| 42 | import android.view.Gravity; |
| 43 | import android.view.KeyEvent; |
| 44 | import android.view.LayoutInflater; |
| 45 | import android.view.Menu; |
| 46 | import android.view.MenuItem; |
| 47 | import android.view.View; |
| 48 | import android.view.View.OnKeyListener; |
| 49 | import android.view.ViewGroup; |
| 50 | import android.view.Window; |
| 51 | import android.view.inputmethod.InputMethodManager; |
| 52 | import android.widget.AdapterView; |
| 53 | import android.widget.BaseAdapter; |
| 54 | import android.widget.Button; |
| 55 | import android.widget.EditText; |
| 56 | import android.widget.ImageButton; |
| 57 | import android.widget.ImageView; |
| 58 | import android.widget.LinearLayout; |
| 59 | import android.widget.ListView; |
| 60 | import android.widget.TextView; |
| 61 | import android.widget.TwoLineListItem; |
| 62 | |
| 63 | public class LookupActivity extends BaseDictionaryActivity { |
| 64 | |
| 65 | private final static String TAG = LookupActivity.class.getName(); |
| 66 | |
| 67 | private Timer timer; |
| 68 | private ListView listView; |
| 69 | private Iterator<Entry> empty = new ArrayList<Entry>().iterator(); |
| 70 | |
| 71 | void updateTitle() { |
| 72 | int dictCount = dictionaryService.getVolumes().size(); |
| 73 | Resources r = getResources(); |
| 74 | String dictionaries = r.getQuantityString(R.plurals.dictionaries, dictCount); |
| 75 | String appName = r.getString(R.string.appName); |
| 76 | String mainTitle = r.getString(R.string.titleLookupActivity, appName, String.format(dictionaries, dictCount)); |
| 77 | setTitle(mainTitle); |
| 78 | } |
| 79 | |
| 80 | @Override |
| 81 | protected void onDestroy() { |
| 82 | super.onDestroy(); |
| 83 | timer.cancel(); |
| 84 | } |
| 85 | |
| 86 | private void updateWordListUI(final Iterator<Entry> results) { |
| 87 | runOnUiThread(new Runnable() { |
| 88 | public void run() { |
| 89 | TextView messageView = (TextView)findViewById(R.id.messageView); |
| 90 | if (!results.hasNext()) { |
| 91 | Editable text = editText.getText(); |
| 92 | if (text != null && !text.toString().equals("")) { |
| 93 | messageView.setText(Html.fromHtml(getString(R.string.nothingFound))); |
| 94 | messageView.setVisibility(View.VISIBLE); |
| 95 | } |
| 96 | else { |
| 97 | messageView.setVisibility(View.GONE); |
| 98 | } |
| 99 | } |
| 100 | else { |
| 101 | messageView.setVisibility(View.GONE); |
| 102 | } |
| 103 | WordAdapter wordAdapter = new WordAdapter(results); |
| 104 | listView.setAdapter(wordAdapter); |
| 105 | listView.setOnItemClickListener(wordAdapter); |
| 106 | setProgressBarIndeterminateVisibility(false); |
| 107 | } |
| 108 | }); |
| 109 | } |
| 110 | |
| 111 | final Runnable updateProgress = new Runnable() { |
| 112 | public void run() { |
| 113 | setProgressBarIndeterminateVisibility(true); |
| 114 | } |
| 115 | }; |
| 116 | |
| 117 | private void doLookup(CharSequence word) { |
| 118 | if (dictionaryService == null) |
| 119 | return; |
| 120 | word = trimLeft(word.toString()); |
| 121 | if (word.equals("")) { |
| 122 | Log.d(TAG, "Nothing to look up"); |
| 123 | updateWordListUI(empty); |
| 124 | return; |
| 125 | } |
| 126 | runOnUiThread(updateProgress); |
| 127 | long t0 = System.currentTimeMillis(); |
| 128 | try { |
| 129 | Iterator<Entry> results = dictionaryService.lookup(word); |
| 130 | Log.d(TAG, "Looked up " + word + " in " |
| 131 | + (System.currentTimeMillis() - t0)); |
| 132 | updateWordListUI(results); |
| 133 | } catch (Exception e) { |
| 134 | StringBuilder msgBuilder = new StringBuilder( |
| 135 | "There was an error while looking up ").append("\"") |
| 136 | .append(word).append("\""); |
| 137 | if (e.getMessage() != null) { |
| 138 | msgBuilder.append(": ").append(e.getMessage()); |
| 139 | } |
| 140 | final String msg = msgBuilder.toString(); |
| 141 | Log.e(TAG, msg, e); |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | private void launchWord(Entry theWord) { |
| 146 | Intent next = new Intent(); |
| 147 | next.setClass(this, ArticleViewActivity.class); |
| 148 | next.putExtra("word", theWord.title); |
| 149 | next.putExtra("section", theWord.section); |
| 150 | next.putExtra("volumeId", theWord.volumeId); |
| 151 | next.putExtra("articlePointer", theWord.articlePointer); |
| 152 | startActivity(next); |
| 153 | } |
| 154 | |
| 155 | |
| 156 | final class WordAdapter extends BaseAdapter implements AdapterView.OnItemClickListener { |
| 157 | |
| 158 | private final List<Entry> words; |
| 159 | private final LayoutInflater mInflater; |
| 160 | private int itemCount; |
| 161 | private Iterator<Entry> results; |
| 162 | private boolean displayMore; |
| 163 | |
| 164 | public WordAdapter(Iterator<Entry> results) { |
| 165 | this.results = results; |
| 166 | this.words = new ArrayList<Entry>(); |
| 167 | loadBatch(); |
| 168 | mInflater = (LayoutInflater) LookupActivity.this.getSystemService( |
| 169 | Context.LAYOUT_INFLATER_SERVICE); |
| 170 | } |
| 171 | |
| 172 | private void loadBatch() { |
| 173 | int count = 0; |
| 174 | while (results.hasNext() && count < 20) { |
| 175 | count++; |
| 176 | words.add(results.next()); |
| 177 | } |
| 178 | displayMore = results.hasNext(); |
| 179 | itemCount = words.size(); |
| 180 | } |
| 181 | |
| 182 | public int getCount() { |
| 183 | return itemCount; |
| 184 | } |
| 185 | |
| 186 | public Object getItem(int position) { |
| 187 | return position; |
| 188 | } |
| 189 | |
| 190 | public long getItemId(int position) { |
| 191 | return position; |
| 192 | } |
| 193 | |
| 194 | public View getView(int position, View convertView, ViewGroup parent) { |
| 195 | if (displayMore && position == itemCount - 1) { |
| 196 | loadMore(position); |
| 197 | } |
| 198 | TwoLineListItem view = (convertView != null) ? (TwoLineListItem) convertView : |
| 199 | createView(parent); |
| 200 | bindView(view, words.get(position)); |
| 201 | return view; |
| 202 | } |
| 203 | |
| 204 | private int loadingMoreForPos; |
| 205 | |
| 206 | private void loadMore(int forPos) { |
| 207 | if (loadingMoreForPos == forPos) { |
| 208 | return; |
| 209 | } |
| 210 | loadingMoreForPos = forPos; |
| 211 | new Thread(new Runnable() { |
| 212 | public void run() { |
| 213 | loadBatch(); |
| 214 | runOnUiThread(new Runnable() { |
| 215 | public void run() { |
| 216 | notifyDataSetChanged(); |
| 217 | } |
| 218 | }); |
| 219 | } |
| 220 | }).start(); |
| 221 | } |
| 222 | |
| 223 | private TwoLineListItem createView(ViewGroup parent) { |
| 224 | TwoLineListItem item; |
| 225 | if (DeviceInfo.EINK_SCREEN) |
| 226 | item = (TwoLineListItem) mInflater.inflate( |
| 227 | R.layout.eink_simple_list_item_2, parent, false); |
| 228 | else |
| 229 | item = (TwoLineListItem) mInflater.inflate( |
| 230 | android.R.layout.simple_list_item_2, parent, false); |
| 231 | item.getText2().setSingleLine(); |
| 232 | item.getText2().setEllipsize(TextUtils.TruncateAt.END); |
| 233 | return item; |
| 234 | } |
| 235 | |
| 236 | private void bindView(TwoLineListItem view, Entry word) { |
| 237 | view.getText1().setText(word.title); |
| 238 | view.getText2().setText(dictionaryService.getDisplayTitle(word.volumeId)); |
| 239 | } |
| 240 | |
| 241 | public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| 242 | launchWord(words.get(position)); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | |
| 247 | final static int MENU_DICT_INFO = 1; |
| 248 | final static int MENU_ABOUT = 2; |
| 249 | final static int MENU_DICT_REFRESH = 3; |
| 250 | private EditText editText; |
| 251 | |
| 252 | private TextWatcher textWatcher; |
| 253 | |
| 254 | @Override |
| 255 | public boolean onCreateOptionsMenu(Menu menu) { |
| 256 | menu.add(0, MENU_DICT_INFO, 0, R.string.mnInfo).setIcon(android.R.drawable.ic_menu_info_details); |
| 257 | menu.add(0, MENU_ABOUT, 0, R.string.mnAbout).setIcon(R.drawable.ic_menu_aarddict); |
| 258 | return true; |
| 259 | } |
| 260 | |
| 261 | @Override |
| 262 | public boolean onOptionsItemSelected(MenuItem item) { |
| 263 | switch (item.getItemId()) { |
| 264 | case MENU_DICT_INFO: |
| 265 | startActivity(new Intent(this, DictionariesActivity.class)); |
| 266 | break; |
| 267 | case MENU_ABOUT: |
| 268 | showAbout(); |
| 269 | break; |
| 270 | } |
| 271 | return true; |
| 272 | } |
| 273 | |
| 274 | private void showAbout() { |
| 275 | PackageManager manager = getPackageManager(); |
| 276 | String versionName = ""; |
| 277 | try { |
| 278 | PackageInfo info = manager.getPackageInfo(getPackageName(), 0); |
| 279 | versionName = info.versionName; |
| 280 | } catch (NameNotFoundException e) { |
| 281 | Log.e(TAG, "Failed to load package info for " + getPackageName(), e) ; |
| 282 | } |
| 283 | |
| 284 | LinearLayout layout = new LinearLayout(this); |
| 285 | layout.setOrientation(LinearLayout.HORIZONTAL); |
| 286 | layout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, |
| 287 | LinearLayout.LayoutParams.FILL_PARENT, 1)); |
| 288 | layout.setPadding(10, 10, 10, 10); |
| 289 | ImageView logo = new ImageView(this); |
| 290 | logo.setImageResource(R.drawable.aarddict); |
| 291 | logo.setPadding(0, 0, 20, 0); |
| 292 | logo.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.FILL_PARENT)); |
| 293 | TextView textView = new TextView(this); |
| 294 | textView.setGravity(Gravity.CENTER_HORIZONTAL); |
| 295 | textView.setLineSpacing(2f, 1); |
| 296 | textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.FILL_PARENT)); |
| 297 | textView.setMovementMethod(LinkMovementMethod.getInstance()); |
| 298 | textView.setText(Html.fromHtml(getString(R.string.about, getString(R.string.appName), versionName))); |
| 299 | |
| 300 | ImageView btnFlattr = new ImageView(this); |
| 301 | btnFlattr.setImageResource(R.drawable.flattr); |
| 302 | btnFlattr.setPadding(5, 5, 5, 5); |
| 303 | btnFlattr.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, |
| 304 | ViewGroup.LayoutParams.WRAP_CONTENT)); |
| 305 | btnFlattr.setOnClickListener(new View.OnClickListener() { |
| 306 | public void onClick(View v) { |
| 307 | Intent browserIntent = new Intent(Intent.ACTION_VIEW, |
| 308 | Uri.parse(getString(R.string.flattrUrl))); |
| 309 | startActivity(browserIntent); |
| 310 | } |
| 311 | }); |
| 312 | |
| 313 | LinearLayout textViewLayout = new LinearLayout(this); |
| 314 | textViewLayout.setOrientation(LinearLayout.VERTICAL); |
| 315 | textViewLayout.setPadding(0, 0, 0, 10); |
| 316 | textViewLayout.addView(textView); |
| 317 | textViewLayout.addView(btnFlattr); |
| 318 | |
| 319 | layout.addView(logo); |
| 320 | layout.addView(textViewLayout); |
| 321 | |
| 322 | AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); |
| 323 | dialogBuilder.setTitle(R.string.titleAbout).setView(layout).setNeutralButton(R.string.btnDismiss, new OnClickListener() { |
| 324 | public void onClick(DialogInterface dialog, int which) { |
| 325 | dialog.dismiss(); |
| 326 | } |
| 327 | }); |
| 328 | dialogBuilder.show(); |
| 329 | } |
| 330 | |
| 331 | @Override |
| 332 | void onDictionaryServiceReady() { |
| 333 | updateTitle(); |
| 334 | Intent intent = getIntent(); |
| 335 | if (intent != null && intent.getAction() != null && intent.getAction().equals(Intent.ACTION_SEARCH)) { |
| 336 | final String word = intent.getStringExtra("query"); |
| 337 | editText.setText(word); |
| 338 | |
| 339 | try { |
| 340 | timer.schedule(new TimerTask() { |
| 341 | @Override |
| 342 | public void run() { |
| 343 | Log.d(TAG, "running lookup task for " + word + " in " + Thread.currentThread()); |
| 344 | doLookup(word); |
| 345 | } |
| 346 | }, 0); |
| 347 | } |
| 348 | catch(IllegalStateException e) { |
| 349 | Log.e(TAG, "Failed to schedule lookup task", e); |
| 350 | } |
| 351 | } |
| 352 | else { |
| 353 | textWatcher.afterTextChanged(editText.getText()); |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | @Override |
| 358 | void onDictionaryOpenFinished() { |
| 359 | onDictionaryServiceReady(); |
| 360 | } |
| 361 | |
| 362 | @Override |
| 363 | void initUI() { |
| 364 | getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS); |
| 365 | |
| 366 | if (DeviceInfo.EINK_SCREEN) |
| 367 | { |
| 368 | setContentView(R.layout.eink_lookup); |
| 369 | listView = (ListView)findViewById(R.id.einkLookupResult); |
| 370 | } |
| 371 | else |
| 372 | { |
| 373 | setContentView(R.layout.lookup); |
| 374 | listView = (ListView)findViewById(R.id.lookupResult); |
| 375 | } |
| 376 | |
| 377 | timer = new Timer(); |
| 378 | |
| 379 | editText = (EditText)findViewById(R.id.wordInput); |
| 380 | |
| 381 | textWatcher = new TextWatcher() { |
| 382 | |
| 383 | TimerTask currentLookupTask; |
| 384 | |
| 385 | public void onTextChanged(CharSequence s, int start, int before, int count) { |
| 386 | } |
| 387 | |
| 388 | public void beforeTextChanged(CharSequence s, int start, int count, |
| 389 | int after) { |
| 390 | } |
| 391 | |
| 392 | public void afterTextChanged(Editable s) { |
| 393 | if (currentLookupTask != null) { |
| 394 | currentLookupTask.cancel(); |
| 395 | } |
| 396 | |
| 397 | final Editable textToLookup = s; |
| 398 | currentLookupTask = new TimerTask() { |
| 399 | @Override |
| 400 | public void run() { |
| 401 | Log.d(TAG, "running lookup task for " + textToLookup + " in " + Thread.currentThread()); |
| 402 | if (textToLookup.equals(editText.getText())) { |
| 403 | doLookup(textToLookup); |
| 404 | } |
| 405 | } |
| 406 | }; |
| 407 | try { |
| 408 | timer.schedule(currentLookupTask, 600); |
| 409 | } |
| 410 | catch(IllegalStateException e) { |
| 411 | //this may happen if orientation changes while loading |
| 412 | Log.d(TAG, "Failed to schedule lookup task", e); |
| 413 | } |
| 414 | } |
| 415 | }; |
| 416 | editText.addTextChangedListener(textWatcher); |
| 417 | |
| 418 | editText.setOnKeyListener(new OnKeyListener() { |
| 419 | public boolean onKey(View v, int keyCode, KeyEvent event) { |
| 420 | if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { |
| 421 | InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); |
| 422 | inputManager.hideSoftInputFromWindow(editText.getApplicationWindowToken(), 0); |
| 423 | return true; |
| 424 | } |
| 425 | return false; |
| 426 | } |
| 427 | }); |
| 428 | |
| 429 | editText.setInputType(InputType.TYPE_CLASS_TEXT); |
| 430 | |
| 431 | Button btnClear = (Button)findViewById(R.id.clearButton); |
| 432 | btnClear.setOnClickListener(new View.OnClickListener() { |
| 433 | public void onClick(View v) { |
| 434 | editText.setText(""); |
| 435 | editText.requestFocus(); |
| 436 | InputMethodManager inputMgr = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); |
| 437 | inputMgr.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT); |
| 438 | } |
| 439 | }); |
| 440 | |
| 441 | } |
| 442 | |
| 443 | static String trimLeft(String s) { |
| 444 | return s.replaceAll("^\\s+", ""); |
| 445 | } |
| 446 | } |