/*
* Copyright (C) 2009-2015 FBReader.ORG Limited <contact@fbreader.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
package org.geometerplus.android.fbreader.bookmark;
import java.util.*;
import android.app.*;
import android.os.*;
import android.view.*;
import android.widget.*;
import android.content.*;
import yuku.ambilwarna.widget.AmbilWarnaPrefWidgetView;
import org.geometerplus.zlibrary.core.util.MiscUtil;
import org.geometerplus.zlibrary.core.resources.ZLResource;
import org.geometerplus.zlibrary.core.options.ZLStringOption;
import org.geometerplus.zlibrary.ui.android.R;
import org.geometerplus.fbreader.book.*;
import org.geometerplus.android.fbreader.FBReader;
import org.geometerplus.android.fbreader.api.FBReaderIntents;
import org.geometerplus.android.fbreader.libraryService.BookCollectionShadow;
import org.geometerplus.android.util.*;
public class BookmarksActivity extends Activity implements IBookCollection.Listener<Book> {
private static final int OPEN_ITEM_ID = 0;
private static final int EDIT_ITEM_ID = 1;
private static final int DELETE_ITEM_ID = 2;
private TabHost myTabHost;
private final Map<Integer,HighlightingStyle> myStyles =
Collections.synchronizedMap(new HashMap<Integer,HighlightingStyle>());
private final BookCollectionShadow myCollection = new BookCollectionShadow();
private volatile Book myBook;
private volatile Bookmark myBookmark;
private final Comparator<Bookmark> myComparator = new Bookmark.ByTimeComparator();
private volatile BookmarksAdapter myThisBookAdapter;
private volatile BookmarksAdapter myAllBooksAdapter;
private volatile BookmarksAdapter mySearchResultsAdapter;
private final ZLResource myResource = ZLResource.resource("bookmarksView");
private final ZLStringOption myBookmarkSearchPatternOption =
new ZLStringOption("BookmarkSearch", "Pattern", "");
private void createTab(String tag, int id) {
final String label = myResource.getResource(tag).getValue();
myTabHost.addTab(myTabHost.newTabSpec(tag).setIndicator(label).setContent(id));
}
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
Thread.setDefaultUncaughtExceptionHandler(new org.geometerplus.zlibrary.ui.android.library.UncaughtExceptionHandler(this));
setContentView(R.layout.bookmarks);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
final SearchManager manager = (SearchManager)getSystemService(SEARCH_SERVICE);
manager.setOnCancelListener(null);
myTabHost = (TabHost)findViewById(R.id.bookmarks_tabhost);
myTabHost.setup();
createTab("thisBook", R.id.bookmarks_this_book);
createTab("allBooks", R.id.bookmarks_all_books);
createTab("search", R.id.bookmarks_search);
myTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
public void onTabChanged(String tabId) {
if ("search".equals(tabId)) {
findViewById(R.id.bookmarks_search_results).setVisibility(View.GONE);
onSearchRequested();
}
}
});
myBook = FBReaderIntents.getBookExtra(getIntent(), myCollection);
if (myBook == null) {
finish();
}
myBookmark = FBReaderIntents.getBookmarkExtra(getIntent());
}
@Override
protected void onStart() {
super.onStart();
myCollection.bindToService(this, new Runnable() {
public void run() {
if (myAllBooksAdapter != null) {
return;
}
myThisBookAdapter =
new BookmarksAdapter((ListView)findViewById(R.id.bookmarks_this_book), myBookmark != null);
myAllBooksAdapter =
new BookmarksAdapter((ListView)findViewById(R.id.bookmarks_all_books), false);
myCollection.addListener(BookmarksActivity.this);
updateStyles();
loadBookmarks();
}
});
OrientationUtil.setOrientation(this, getIntent());
}
private void updateStyles() {
synchronized (myStyles) {
myStyles.clear();
for (HighlightingStyle style : myCollection.highlightingStyles()) {
myStyles.put(style.Id, style);
}
}
}
private final Object myBookmarksLock = new Object();
private void loadBookmarks() {
new Thread(new Runnable() {
public void run() {
synchronized (myBookmarksLock) {
for (BookmarkQuery query = new BookmarkQuery(myBook, 50); ; query = query.next()) {
final List<Bookmark> thisBookBookmarks = myCollection.bookmarks(query);
if (thisBookBookmarks.isEmpty()) {
break;
}
myThisBookAdapter.addAll(thisBookBookmarks);
myAllBooksAdapter.addAll(thisBookBookmarks);
}
for (BookmarkQuery query = new BookmarkQuery(50); ; query = query.next()) {
final List<Bookmark> allBookmarks = myCollection.bookmarks(query);
if (allBookmarks.isEmpty()) {
break;
}
myAllBooksAdapter.addAll(allBookmarks);
}
}
}
}).start();
}
private void updateBookmarks(final Book book) {
new Thread(new Runnable() {
public void run() {
synchronized (myBookmarksLock) {
final boolean flagThisBookTab = book.getId() == myBook.getId();
final boolean flagSearchTab = mySearchResultsAdapter != null;
final Map<String,Bookmark> oldBookmarks = new HashMap<String,Bookmark>();
if (flagThisBookTab) {
for (Bookmark b : myThisBookAdapter.bookmarks()) {
oldBookmarks.put(b.Uid, b);
}
} else {
for (Bookmark b : myAllBooksAdapter.bookmarks()) {
if (b.BookId == book.getId()) {
oldBookmarks.put(b.Uid, b);
}
}
}
final String pattern = myBookmarkSearchPatternOption.getValue().toLowerCase();
for (BookmarkQuery query = new BookmarkQuery(book, 50); ; query = query.next()) {
final List<Bookmark> loaded = myCollection.bookmarks(query);
if (loaded.isEmpty()) {
break;
}
for (Bookmark b : loaded) {
final Bookmark old = oldBookmarks.remove(b.Uid);
myAllBooksAdapter.replace(old, b);
if (flagThisBookTab) {
myThisBookAdapter.replace(old, b);
}
if (flagSearchTab && MiscUtil.matchesIgnoreCase(b.getText(), pattern)) {
mySearchResultsAdapter.replace(old, b);
}
}
}
myAllBooksAdapter.removeAll(oldBookmarks.values());
if (flagThisBookTab) {
myThisBookAdapter.removeAll(oldBookmarks.values());
}
if (flagSearchTab) {
mySearchResultsAdapter.removeAll(oldBookmarks.values());
}
}
}
}).start();
}
@Override
protected void onNewIntent(Intent intent) {
OrientationUtil.setOrientation(this, intent);
if (!Intent.ACTION_SEARCH.equals(intent.getAction())) {
return;
}
String pattern = intent.getStringExtra(SearchManager.QUERY);
myBookmarkSearchPatternOption.setValue(pattern);
final LinkedList<Bookmark> bookmarks = new LinkedList<Bookmark>();
pattern = pattern.toLowerCase();
for (Bookmark b : myAllBooksAdapter.bookmarks()) {
if (MiscUtil.matchesIgnoreCase(b.getText(), pattern)) {
bookmarks.add(b);
}
}
if (!bookmarks.isEmpty()) {
final ListView resultsView = (ListView)findViewById(R.id.bookmarks_search_results);
resultsView.setVisibility(View.VISIBLE);
if (mySearchResultsAdapter == null) {
mySearchResultsAdapter = new BookmarksAdapter(resultsView, false);
} else {
mySearchResultsAdapter.clear();
}
mySearchResultsAdapter.addAll(bookmarks);
} else {
UIMessageUtil.showErrorMessage(this, "bookmarkNotFound");
}
}
@Override
protected void onDestroy() {
myCollection.unbind();
super.onDestroy();
}
@Override
public boolean onSearchRequested() {
if (DeviceType.Instance().hasStandardSearchDialog()) {
startSearch(myBookmarkSearchPatternOption.getValue(), true, null, false);
} else {
SearchDialogUtil.showDialog(this, BookmarksActivity.class, myBookmarkSearchPatternOption.getValue(), null);
}
return true;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
final int position = ((AdapterView.AdapterContextMenuInfo)item.getMenuInfo()).position;
final String tag = myTabHost.getCurrentTabTag();
final BookmarksAdapter adapter;
if ("thisBook".equals(tag)) {
adapter = myThisBookAdapter;
} else if ("allBooks".equals(tag)) {
adapter = myAllBooksAdapter;
} else if ("search".equals(tag)) {
adapter = mySearchResultsAdapter;
} else {
throw new RuntimeException("Unknown tab tag: " + tag);
}
final Bookmark bookmark = adapter.getItem(position);
switch (item.getItemId()) {
case OPEN_ITEM_ID:
gotoBookmark(bookmark);
return true;
case EDIT_ITEM_ID:
final Intent intent = new Intent(this, EditBookmarkActivity.class);
FBReaderIntents.putBookmarkExtra(intent, bookmark);
OrientationUtil.startActivity(this, intent);
return true;
case DELETE_ITEM_ID:
myCollection.deleteBookmark(bookmark);
return true;
}
return super.onContextItemSelected(item);
}
private void gotoBookmark(Bookmark bookmark) {
bookmark.markAsAccessed();
myCollection.saveBookmark(bookmark);
final Book book = myCollection.getBookById(bookmark.BookId);
if (book != null) {
FBReader.openBookActivity(this, book, bookmark);
} else {
UIMessageUtil.showErrorMessage(this, "cannotOpenBook");
}
}
private final class BookmarksAdapter extends BaseAdapter implements AdapterView.OnItemClickListener, View.OnCreateContextMenuListener {
private final List<Bookmark> myBookmarksList =
Collections.synchronizedList(new LinkedList<Bookmark>());
private volatile boolean myShowAddBookmarkItem;
BookmarksAdapter(ListView listView, boolean showAddBookmarkItem) {
myShowAddBookmarkItem = showAddBookmarkItem;
listView.setAdapter(this);
listView.setOnItemClickListener(this);
listView.setOnCreateContextMenuListener(this);
}
public List<Bookmark> bookmarks() {
return Collections.unmodifiableList(myBookmarksList);
}
public void addAll(final List<Bookmark> bookmarks) {
runOnUiThread(new Runnable() {
public void run() {
synchronized (myBookmarksList) {
for (Bookmark b : bookmarks) {
final int position = Collections.binarySearch(myBookmarksList, b, myComparator);
if (position < 0) {
myBookmarksList.add(- position - 1, b);
}
}
}
notifyDataSetChanged();
}
});
}
private boolean areEqualsForView(Bookmark b0, Bookmark b1) {
return
b0.getStyleId() == b1.getStyleId() &&
b0.getText().equals(b1.getText()) &&
b0.getTimestamp(Bookmark.DateType.Latest).equals(b1.getTimestamp(Bookmark.DateType.Latest));
}
public void replace(final Bookmark old, final Bookmark b) {
if (old != null && areEqualsForView(old, b)) {
return;
}
runOnUiThread(new Runnable() {
public void run() {
synchronized (myBookmarksList) {
if (old != null) {
myBookmarksList.remove(old);
}
final int position = Collections.binarySearch(myBookmarksList, b, myComparator);
if (position < 0) {
myBookmarksList.add(- position - 1, b);
}
}
notifyDataSetChanged();
}
});
}
public void removeAll(final Collection<Bookmark> bookmarks) {
if (bookmarks.isEmpty()) {
return;
}
runOnUiThread(new Runnable() {
public void run() {
myBookmarksList.removeAll(bookmarks);
notifyDataSetChanged();
}
});
}
public void clear() {
runOnUiThread(new Runnable() {
public void run() {
myBookmarksList.clear();
notifyDataSetChanged();
}
});
}
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
final int position = ((AdapterView.AdapterContextMenuInfo)menuInfo).position;
if (getItem(position) != null) {
menu.add(0, OPEN_ITEM_ID, 0, myResource.getResource("openBook").getValue());
menu.add(0, EDIT_ITEM_ID, 0, myResource.getResource("editBookmark").getValue());
menu.add(0, DELETE_ITEM_ID, 0, myResource.getResource("deleteBookmark").getValue());
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View view = (convertView != null) ? convertView :
LayoutInflater.from(parent.getContext()).inflate(R.layout.bookmark_item, parent, false);
final ImageView imageView = ViewUtil.findImageView(view, R.id.bookmark_item_icon);
final View colorContainer = ViewUtil.findView(view, R.id.bookmark_item_color_container);
final AmbilWarnaPrefWidgetView colorView =
(AmbilWarnaPrefWidgetView)ViewUtil.findView(view, R.id.bookmark_item_color);
final TextView textView = ViewUtil.findTextView(view, R.id.bookmark_item_text);
final TextView bookTitleView = ViewUtil.findTextView(view, R.id.bookmark_item_booktitle);
final Bookmark bookmark = getItem(position);
if (bookmark == null) {
imageView.setVisibility(View.VISIBLE);
imageView.setImageResource(R.drawable.ic_list_plus);
colorContainer.setVisibility(View.GONE);
textView.setText(myResource.getResource("new").getValue());
bookTitleView.setVisibility(View.GONE);
} else {
imageView.setVisibility(View.GONE);
colorContainer.setVisibility(View.VISIBLE);
BookmarksUtil.setupColorView(colorView, myStyles.get(bookmark.getStyleId()));
textView.setText(bookmark.getText());
if (myShowAddBookmarkItem) {
bookTitleView.setVisibility(View.GONE);
} else {
bookTitleView.setVisibility(View.VISIBLE);
bookTitleView.setText(bookmark.BookTitle);
}
}
return view;
}
@Override
public final boolean areAllItemsEnabled() {
return true;
}
@Override
public final boolean isEnabled(int position) {
return true;
}
@Override
public final long getItemId(int position) {
final Bookmark item = getItem(position);
return item != null ? item.getId() : -1;
}
@Override
public final Bookmark getItem(int position) {
if (myShowAddBookmarkItem) {
--position;
}
return position >= 0 ? myBookmarksList.get(position) : null;
}
@Override
public final int getCount() {
return myShowAddBookmarkItem ? myBookmarksList.size() + 1 : myBookmarksList.size();
}
public final void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Bookmark bookmark = getItem(position);
if (bookmark != null) {
gotoBookmark(bookmark);
} else if (myShowAddBookmarkItem) {
myShowAddBookmarkItem = false;
myCollection.saveBookmark(myBookmark);
}
}
}
// method from IBookCollection.Listener
public void onBookEvent(BookEvent event, Book book) {
switch (event) {
default:
break;
case BookmarkStyleChanged:
runOnUiThread(new Runnable() {
public void run() {
updateStyles();
myAllBooksAdapter.notifyDataSetChanged();
myThisBookAdapter.notifyDataSetChanged();
if (mySearchResultsAdapter != null) {
mySearchResultsAdapter.notifyDataSetChanged();
}
}
});
break;
case BookmarksUpdated:
updateBookmarks(book);
break;
}
}
// method from IBookCollection.Listener
public void onBuildEvent(IBookCollection.Status status) {
}
}