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 | } |