package com.orgzly.android.ui.fragments;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v7.view.ActionMode;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.ViewFlipper;
import com.orgzly.BuildConfig;
import com.orgzly.R;
import com.orgzly.android.Book;
import com.orgzly.android.Shelf;
import com.orgzly.android.prefs.AppPreferences;
import com.orgzly.android.provider.ProviderContract;
import com.orgzly.android.provider.clients.BooksClient;
import com.orgzly.android.ui.ActionModeListener;
import com.orgzly.android.ui.Fab;
import com.orgzly.android.ui.HeadsListViewAdapter;
import com.orgzly.android.ui.Loaders;
import com.orgzly.android.ui.NotePlace;
import com.orgzly.android.ui.NoteStateSpinner;
import com.orgzly.android.ui.Place;
import com.orgzly.android.ui.Selection;
import com.orgzly.android.ui.dialogs.TimestampDialogFragment;
import com.orgzly.android.ui.views.GesturedListView;
import com.orgzly.android.util.LogUtils;
import com.orgzly.org.datetime.OrgDateTime;
import java.util.Set;
import java.util.TreeSet;
/**
* Displays all notes from the notebook.
* Allows moving, cutting, pasting etc.
*/
public class BookFragment extends NoteListFragment
implements
Fab,
TimestampDialogFragment.OnDateTimeSetListener,
LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = BookFragment.class.getName();
/** Name used for {@link android.app.FragmentManager}. */
public static final String FRAGMENT_TAG = BookFragment.class.getName();
/* Arguments. */
private static final String ARG_BOOK_ID = "bookId";
private static final String ARG_NOTE_ID = "noteId";
private static final int STATE_ITEM_GROUP = 1;
private static final int[] ITEMS_HIDDEN_ON_MULTIPLE_SELECTED_NOTES = new int[] {
R.id.book_cab_new,
R.id.book_cab_cut,
R.id.book_cab_paste,
R.id.book_cab_move
};
private BookFragmentListener listener;
private boolean mIsViewCreated = false;
private Book mBook;
private Long mBookId;
private static long mLastBookId;
private View mHeader;
private TextView mPrefaceText;
private View mNoNotesText;
private SimpleCursorAdapter mListAdapter;
private String mActionModeTag;
/** Used to switch to book-does-not-exist view, if the book has been deleted. */
private ViewFlipper mViewFlipper;
/**
* @param bookId Book ID
* @param noteId Set position (scroll to) this note, if greater then zero
*/
public static BookFragment getInstance(long bookId, long noteId) {
BookFragment fragment = new BookFragment();
Bundle args = new Bundle();
args.putLong(BookFragment.ARG_BOOK_ID, bookId);
args.putLong(BookFragment.ARG_NOTE_ID, noteId);
fragment.setArguments(args);
return fragment;
}
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public BookFragment() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
}
@Override
public void onAttach(Context context) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, context);
super.onAttach(context);
/* This makes sure that the container activity has implemented
* the callback interface. If not, it throws an exception
*/
try {
listener = (BookFragmentListener) getActivity();
} catch (ClassCastException e) {
throw new ClassCastException(getActivity().toString() + " must implement " + BookFragmentListener.class);
}
try {
mActionModeListener = (ActionModeListener) getActivity();
} catch (ClassCastException e) {
throw new ClassCastException(getActivity().toString() + " must implement " + ActionModeListener.class);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, savedInstanceState);
super.onCreate(savedInstanceState);
/* Would like to add items to the Options Menu.
* Required (for fragments only) to receive onCreateOptionsMenu() call.
*/
setHasOptionsMenu(true);
parseArguments();
if (savedInstanceState != null && savedInstanceState.getBoolean("actionModeMove", false)) {
mActionModeTag = "M";
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_book, container, false);
final ListView listView = (ListView) view.findViewById(android.R.id.list);
// setupGestureDetector(listView);
mHeader = inflater.inflate(R.layout.item_head_book_preface, listView, false);
mPrefaceText = (TextView) mHeader.findViewById(R.id.fragment_book_header_text);
if (getActivity() != null && AppPreferences.isFontMonospaced(getContext())) {
mPrefaceText.setTypeface(Typeface.MONOSPACE);
}
mViewFlipper = (ViewFlipper) view.findViewById(R.id.fragment_book_view_flipper);
/* Big new note button when there are no notes. */
mNoNotesText = view.findViewById(R.id.fragment_book_no_notes);
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, view, savedInstanceState);
super.onViewCreated(view, savedInstanceState);
mIsViewCreated = true;
/* Long click listener. */
getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (position > getListView().getHeaderViewsCount() - 1) { /* Not a header. */
listener.onNoteLongClick(BookFragment.this, view, position, id);
return true;
} else {
return false;
}
}
});
/* Item toolbar listener. */
getListView().setOnItemMenuButtonClickListener(
new GesturedListView.OnItemMenuButtonClickListener() {
@Override
public boolean onMenuButtonClick(int buttonId, long noteId) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, buttonId, noteId);
switch (buttonId) {
case R.id.item_menu_schedule_btn:
displayScheduleTimestampDialog(R.id.item_menu_schedule_btn, noteId);
break;
case R.id.item_menu_prev_state_btn:
listener.onStateCycleRequest(noteId, -1);
break;
case R.id.item_menu_next_state_btn:
listener.onStateCycleRequest(noteId, 1);
break;
case R.id.item_menu_done_state_btn:
listener.onStateToDoneRequest(noteId);
break;
case R.id.item_menu_delete_btn:
TreeSet<Long> ids = new TreeSet<>();
ids.add(noteId);
delete(ids);
/* Remove selection. */
mSelection.clearSelection();
break;
case R.id.item_menu_new_above_btn:
listener.onNoteNewRequest(new NotePlace(mBookId, noteId, Place.ABOVE));
break;
case R.id.item_menu_new_under_btn:
listener.onNoteNewRequest(new NotePlace(mBookId, noteId, Place.UNDER));
break;
case R.id.item_menu_new_below_btn:
listener.onNoteNewRequest(new NotePlace(mBookId, noteId, Place.BELOW));
break;
}
return false;
}
});
/* If it's not set to null, we get java.lang.IllegalStateException:
* Cannot add header view to list -- setAdapter has already been called.
* This happens when trying to set header for list, when getting fragment from back stack.
*/
setListAdapter(null);
/* Add selectable header to the list view. */
getListView().addHeaderView(mHeader, null, true);
/* Create a selection. Must be aware of number of headers. */
mSelection = new Selection();
mListAdapter = new HeadsListViewAdapter(getActivity(), mSelection, getListView().getItemMenus(), true);
setListAdapter(mListAdapter);
mSelection.restoreIds(savedInstanceState);
/* Reset from ids will be performed after loading the data. */
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, savedInstanceState);
super.onActivityCreated(savedInstanceState);
/* Activity created - context available. Create Shelf and populate list with data. */
mShelf = new Shelf(getActivity().getApplicationContext());
/*
* If loader is for last loaded book, just init, do not restart.
* Trying not to reset the loader and null the adapter, to keep the scroll position.
*
* NOTE: Opening a new book fragment for the same book will not restore the previous
* fragment's list view, even though book ID is the same. This is because callbacks
* will be different (they are two separate fragments displaying the same book).
*/
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "Book Loader: Last book " + mLastBookId + ", new book " + mBookId);
if (mLastBookId == mBookId) {
getActivity().getSupportLoaderManager().initLoader(Loaders.BOOK_FRAGMENT_BOOK, null, this);
getActivity().getSupportLoaderManager().initLoader(Loaders.BOOK_FRAGMENT_NOTES, null, this);
} else {
getActivity().getSupportLoaderManager().restartLoader(Loaders.BOOK_FRAGMENT_BOOK, null, this);
getActivity().getSupportLoaderManager().restartLoader(Loaders.BOOK_FRAGMENT_NOTES, null, this);
}
}
@Override
public void onPause() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
super.onPause();
/*
* Stop list from scrolling.
*
* Workaround to avoid:
*
* "java.lang.IllegalStateException: attempt to re-open an already-closed object".
*
* This happens after fling, when opening a different book from the drawer while the list
* is still scrolling. This can also be avoided by destroying loaders here, but then we
* would lose book's scrolling position.
*/
getListView().dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0));
}
@Override
public void onDestroyView() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
super.onDestroyView();
mIsViewCreated = false;
}
@Override
public void onDetach() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
super.onDetach();
listener = null;
mActionModeListener = null;
}
private void parseArguments() {
if (getArguments() == null) {
throw new IllegalArgumentException("No arguments found to " + BookFragment.class.getSimpleName());
}
/* Book ID. */
if (!getArguments().containsKey(ARG_BOOK_ID)) {
throw new IllegalArgumentException(BookFragment.class.getSimpleName() + " requires "+ ARG_BOOK_ID + " argument passed");
}
mBookId = getArguments().getLong(ARG_BOOK_ID);
if (mBookId <= 0) {
throw new IllegalArgumentException("Passed argument book id is not valid (" + mBookId + ")");
}
}
@Override
public void onListItemClick(ListView listView, View view, int position, long id) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, listView, view, position, id);
if ((position+1) > listView.getHeaderViewsCount()) { /* Not a header. */
listener.onNoteClick(this, view, position, id);
} else {
listener.onBookPrefaceEditRequest(mBook);
}
}
/*
* Options menu.
*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, menu, inflater);
inflater.inflate(R.menu.book_actions, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
/* Remove some menu items if book doesn't exist or it doesn't contain any notes. */
if (mBook == null || mListAdapter.getCount() == 0) {
menu.removeItem(R.id.books_options_menu_item_cycle_visibility);
}
if (mBook == null) {
menu.removeItem(R.id.books_options_menu_book_preface);
}
// /* Toggle paste item visibility. */
// item = menu.findItem(R.id.books_options_menu_item_paste);
// if (item != null && mShelf != null) {
// item.setVisible(mShelf.haveCutNotes());
// }
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, item);
switch (item.getItemId()) {
case R.id.books_options_menu_item_cycle_visibility:
listener.onCycleVisibilityRequest(mBook);
return true;
case R.id.books_options_menu_book_preface:
listener.onBookPrefaceEditRequest(mBook);
return true;
// case R.id.books_options_menu_item_paste:
// mListener.onNotesPasteRequest(mBookId, 0, null);
// return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onDateTimeSet(int id, TreeSet<Long> noteIds, OrgDateTime time) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, id, time);
switch (id) {
case R.id.book_cab_schedule:
case R.id.item_menu_schedule_btn:
listener.onScheduledTimeUpdateRequest(noteIds, time);
break;
}
}
@Override
public void onDateTimeCleared(int id, TreeSet<Long> noteIds) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, id);
switch (id) {
case R.id.book_cab_schedule:
case R.id.item_menu_schedule_btn:
listener.onScheduledTimeUpdateRequest(noteIds, null);
break;
}
}
@Override
public void onDateTimeAborted(int id, TreeSet<Long> noteIds) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, id);
}
/*
* Actions
*/
private void newNoteRelativeToSelection(Place place) {
long targetNoteId = getTargetNoteIdFromSelection(place);
listener.onNoteNewRequest(new NotePlace(mBookId, targetNoteId, place));
}
// TODO: Go through main activity and do it in background.
private int moveNotes(int offset) {
/* Sanity check. Should not ever happen. */
if (getSelection().getCount() == 0) {
Log.e(TAG, "Trying to move notes up while there are no notes selected");
return 0;
}
return BooksClient.moveNotes(getContext(), mBookId, getSelection().getIds().first(), offset);
}
/**
* Paste notes.
* @param place {@link Place}
*/
private void pasteNotes(Place place) {
long noteId = getTargetNoteIdFromSelection(place);
/* Remove selection. */
mSelection.clearSelection();
listener.onNotesPasteRequest(mBookId, noteId, place);
}
private void scrollToNoteIfSet() {
long noteId = getArguments().getLong(ARG_NOTE_ID, 0);
if (noteId > 0) {
long t = System.currentTimeMillis();
for (int i = 0; i < getListAdapter().getCount(); i++) {
long id = getListAdapter().getItemId(i);
if (id == noteId) {
scrollToCursorPosition(i);
/* Make sure we don't scroll again (for example after configuration change). */
getArguments().remove(ARG_NOTE_ID);
break;
}
}
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG,
"Scrolling to note " + noteId +
" took " + (System.currentTimeMillis() - t) + "ms");
}
}
/**
* @param cursorPosition note to scroll to. 0 for first note, 1 for second etc.
*/
private void scrollToCursorPosition(final int cursorPosition) {
getListView().post(new Runnable() {
public void run() {
// getListView().scrollTo(0, getListView().getBottom());
// getListView().smoothScrollToPosition(cursorPosition + getListView().getHeaderViewsCount());
getListView().setSelection(cursorPosition + getListView().getHeaderViewsCount());
}
});
}
public Book getBook() {
return mBook;
}
@Override
public String getFragmentTag() {
return FRAGMENT_TAG;
}
@Override
public ActionMode.Callback getNewActionMode() {
return new MyActionMode();
}
/*
* Loading ...
*/
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, id, bundle);
switch (id) {
case Loaders.BOOK_FRAGMENT_BOOK:
return new CursorLoader(
getActivity().getApplicationContext(),
ProviderContract.Books.ContentUri.booksId(mBookId),
null,
null,
null,
null
);
case Loaders.BOOK_FRAGMENT_NOTES:
/* Get all non-cut notes for book id. Order by position. */
return new CursorLoader(
getActivity().getApplicationContext(),
ProviderContract.Books.ContentUri.booksIdNotes(mBookId),
null,
null,
null,
ProviderContract.Notes.QueryParam.LFT);
default:
throw new IllegalArgumentException("Unknown loader id " + id);
}
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, cursorLoader, cursor);
if (mIsViewCreated) {
if (cursorLoader.getId() == Loaders.BOOK_FRAGMENT_BOOK) {
Book book = null;
if (cursor.moveToFirst()) {
book = BooksClient.fromCursor(cursor);
}
bookLoaded(book);
mLastBookId = mBookId;
} else if (cursorLoader.getId() == Loaders.BOOK_FRAGMENT_NOTES) {
notesLoaded(cursor);
}
/* Refresh action bar items (hide or display, depending on if the book has been loaded. */
FragmentActivity activity = getActivity();
if (activity != null) {
activity.supportInvalidateOptionsMenu();
}
}
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, cursorLoader);
if (mIsViewCreated) {
if (cursorLoader.getId() == Loaders.BOOK_FRAGMENT_NOTES) {
mListAdapter.changeCursor(null);
} else if (cursorLoader.getId() == Loaders.BOOK_FRAGMENT_BOOK) {
/* Nothing holds the cursor. */
}
}
}
private void bookLoaded(Book book) {
/* Set the current book.
* Book cab be null. That can happen when this fragment is in back stack and its book
* gets deleted. When fragment is popped from the back stack it will try to fetch the
* non-existent book id.
*/
mBook = book;
if (mBook != null) {
updatePreface();
}
mViewFlipper.setDisplayedChild(book != null ? 0 : 1);
announceChangesToActivity();
}
private void announceChangesToActivity() {
if (listener != null) {
listener.announceChanges(
BookFragment.FRAGMENT_TAG,
Book.getFragmentTitleForBook(mBook),
Book.getFragmentSubtitleForBook(mBook),
mSelection.getCount());
}
}
/**
* Update book's preface.
*/
private void updatePreface() {
boolean displayPreface = ! getString(R.string.pref_value_preface_in_book_hide)
.equals(AppPreferences.prefaceDisplay(getContext()));
if (! TextUtils.isEmpty(mBook.getPreface()) && displayPreface) {
// Add header
if (getListView().getHeaderViewsCount() == 0) {
getListView().addHeaderView(mHeader);
}
if (getString(R.string.pref_value_preface_in_book_few_lines)
.equals(AppPreferences.prefaceDisplay(getContext()))) {
mPrefaceText.setMaxLines(3);
mPrefaceText.setEllipsize(TextUtils.TruncateAt.END);
} else {
mPrefaceText.setMaxLines(Integer.MAX_VALUE);
mPrefaceText.setEllipsize(null);
}
mPrefaceText.setText(mBook.getPreface());
} else {
// Remove header
if (getListView().getHeaderViewsCount() > 0) {
getListView().removeHeaderView(mHeader);
}
}
}
private void notesLoaded(Cursor cursor) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, cursor);
/**
* Swapping instead of changing Cursor here, to keep the old one open.
* Loader should release the old Cursor - see note in
* {@link LoaderManager.LoaderCallbacks#onLoadFinished).
*/
mListAdapter.swapCursor(cursor);
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "after swap: cursor/adapter count: " + cursor.getCount() + "/" + mListAdapter.getCount());
if (mListAdapter.getCount() > 0) {
mNoNotesText.setVisibility(View.GONE);
} else {
mNoNotesText.setVisibility(View.VISIBLE);
}
if (mActionModeListener != null) {
mActionModeListener.updateActionModeForSelection(mSelection, new MyActionMode());
ActionMode actionMode = mActionModeListener.getActionMode();
if (mActionModeTag != null) {
actionMode.setTag("M"); // TODO: Ugh.
actionMode.invalidate();
mActionModeTag = null;
}
}
/* Scroll to note if note id argument is set. */
scrollToNoteIfSet();
}
@Override
public Runnable getFabAction() {
return mBook != null ? new Runnable() {
@Override
public void run() {
listener.onNoteNewRequest(new NotePlace(mBookId));
}
} : null;
}
private void delete(final TreeSet<Long> ids) {
new AlertDialog.Builder(getContext())
.setTitle(R.string.delete_notes)
.setMessage(R.string.delete_notes_and_all_subnotes)
.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
listener.onNotesDeleteRequest(mBookId, ids);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.create()
.show();
}
public class MyActionMode implements ActionMode.Callback {
@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, actionMode, menu);
/* Inflate a menu resource providing context menu items. */
MenuInflater inflater = actionMode.getMenuInflater();
inflater.inflate(R.menu.book_cab, menu);
return true;
}
/**
* Called each time the action mode is shown. Always called after onCreateActionMode,
* but may be called multiple times if the mode is invalidated.
*/
@Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, actionMode, menu);
/* Update action mode with number of selected items. */
actionMode.setTitle(String.valueOf(mSelection.getCount()));
/* Movement menu. */
if ("M".equals(actionMode.getTag())) { /* Tag could be null, which is fine here. */
menu.clear();
MenuInflater inflater = actionMode.getMenuInflater();
inflater.inflate(R.menu.book_cab_moving, menu);
}
/* Hide some items if multiple notes are selected. */
for (int id: ITEMS_HIDDEN_ON_MULTIPLE_SELECTED_NOTES) {
MenuItem item = menu.findItem(id);
if (item != null) {
item.setVisible(mSelection.getCount() == 1);
}
}
if (actionMode.getTag() != null && mSelection.getCount() > 1) {
menu.clear();
MenuInflater inflater = actionMode.getMenuInflater();
inflater.inflate(R.menu.book_cab, menu);
actionMode.setTag(null);
}
announceChangesToActivity();
return true;
}
@Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, actionMode, menuItem);
switch (menuItem.getItemId()) {
case R.id.book_cab_new_above:
newNoteRelativeToSelection(Place.ABOVE);
actionMode.finish(); /* Close action mode. */
break;
case R.id.book_cab_new_under:
newNoteRelativeToSelection(Place.UNDER);
actionMode.finish(); /* Close action mode. */
break;
case R.id.book_cab_new_below:
newNoteRelativeToSelection(Place.BELOW);
actionMode.finish(); /* Close action mode. */
break;
case R.id.book_cab_move:
/* TODO Select all descendants of selected notes. */
/* Request different menu for this action mode. */
actionMode.setTag("M");
actionMode.invalidate();
break;
case R.id.book_cab_schedule:
displayScheduleTimestampDialog(R.id.book_cab_schedule, mSelection.getIds());
break;
case R.id.book_cab_cut:
case R.id.book_cab_delete_note:
/* Get currently selected notes' IDs. */
TreeSet<Long> ids = new TreeSet<>();
ids.addAll(mSelection.getIds());
if (menuItem.getItemId() == R.id.book_cab_cut) {
listener.onNotesCutRequest(mBookId, ids);
} else {
delete(ids);
}
/* Remove selection. */
mSelection.clearSelection();
actionMode.finish(); /* Close action mode. */
break;
// case R.id.book_cab_undo:
// undoCut();
// actionMode.finish(); /* Close action mode. */
// break;
case R.id.book_cab_paste_above:
pasteNotes(Place.ABOVE);
actionMode.finish(); /* Close action mode. */
break;
case R.id.book_cab_paste_under:
pasteNotes(Place.UNDER);
actionMode.finish(); /* Close action mode. */
break;
case R.id.book_cab_paste_below:
pasteNotes(Place.BELOW);
actionMode.finish(); /* Close action mode. */
break;
case R.id.notes_action_move_up:
moveNotes(-1);
break;
case R.id.notes_action_move_down:
moveNotes(1);
break;
case R.id.notes_action_move_left:
listener.onNotesPromoteRequest(mBookId, mSelection.getIds());
break;
case R.id.notes_action_move_right:
listener.onNotesDemoteRequest(mBookId, mSelection.getIds());
break;
case R.id.book_cab_state:
/* Add all known states to menu. */
SubMenu subMenu = menuItem.getSubMenu();
if (subMenu != null) {
subMenu.clear();
for (String str: new NoteStateSpinner(getActivity(), null).getValues()) {
subMenu.add(STATE_ITEM_GROUP, Menu.NONE, Menu.NONE, str);
}
}
break;
default:
/* State. */
if (menuItem.getGroupId() == STATE_ITEM_GROUP) {
listener.onStateChangeRequest(mSelection.getIds(), menuItem.getTitle().toString());
return true;
}
return false; // Not handled.
}
return true; // Handled.
}
@Override
public void onDestroyActionMode(ActionMode actionMode) {
mSelection.clearSelection();
/* List adapter could be null, as we could be destroying the action mode of a fragment
* which is in back stack. That fragment had its onDestroyView called, where list
* adapter is set to null.
*/
if (getListAdapter() != null) {
getListAdapter().notifyDataSetChanged();
}
mActionModeListener.actionModeDestroyed();
announceChangesToActivity();
}
}
public interface BookFragmentListener extends NoteListFragmentListener {
void onBookPrefaceEditRequest(Book book);
void onNotesDeleteRequest(long bookId, TreeSet<Long> noteIds);
void onNotesCutRequest(long bookId, TreeSet<Long> noteIds);
void onNotesPasteRequest(long bookId, long noteId, Place place);
void onNotesPromoteRequest(long bookId, Set<Long> noteIds);
void onNotesDemoteRequest(long bookId, Set<Long> noteIds);
void onCycleVisibilityRequest(Book book);
}
}