com.eleybourn.bookcatalogue.BookEdit.java Source code

Java tutorial

Introduction

Here is the source code for com.eleybourn.bookcatalogue.BookEdit.java

Source

/*
 * @copyright 2010 Evan Leybourn
 * @license GNU General Public License
 * 
 * This file is part of Book Catalogue.
 *
 * Book Catalogue 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 3 of the License, or
 * (at your option) any later version.
 *
 * Book Catalogue 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 Book Catalogue.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.eleybourn.bookcatalogue;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.view.MenuItem;
import com.eleybourn.bookcatalogue.booklist.BooklistBuilder;
import com.eleybourn.bookcatalogue.booklist.FlattenedBooklist;
import com.eleybourn.bookcatalogue.compat.BookCatalogueActivity;
import com.eleybourn.bookcatalogue.compat.BookCatalogueFragment;
import com.eleybourn.bookcatalogue.datamanager.DataEditor;
import com.eleybourn.bookcatalogue.debug.Tracker;
import com.eleybourn.bookcatalogue.dialogs.BookshelfDialogFragment;
import com.eleybourn.bookcatalogue.dialogs.BookshelfDialogFragment.OnBookshelfCheckChangeListener;
import com.eleybourn.bookcatalogue.dialogs.PartialDatePickerFragment;
import com.eleybourn.bookcatalogue.dialogs.PartialDatePickerFragment.OnPartialDatePickerListener;
import com.eleybourn.bookcatalogue.dialogs.StandardDialogs;
import com.eleybourn.bookcatalogue.dialogs.TextFieldEditorFragment;
import com.eleybourn.bookcatalogue.dialogs.TextFieldEditorFragment.OnTextFieldEditorListener;
import com.eleybourn.bookcatalogue.utils.Logger;
import com.eleybourn.bookcatalogue.utils.Utils;
//import android.app.LocalActivityManager;

/**
 * A tab host activity which holds the three edit book tabs 1. Edit Details /
 * Book Details 2. Edit Comments 3. Loan Book
 * 
 * @author Evan Leybourn
 */
public class BookEdit extends BookCatalogueActivity implements BookEditFragmentAbstract.BookEditManager,
        OnPartialDatePickerListener, OnTextFieldEditorListener, OnBookshelfCheckChangeListener {
    private FlattenedBooklist mList = null;
    private GestureDetector mGestureDetector;

    private boolean mIsDirtyFlg = false;

    public static final String TAB = "tab";
    public static final int TAB_EDIT = 0;
    public static final int TAB_EDIT_NOTES = 1;
    public static final int TAB_EDIT_FRIENDS = 2;
    private String added_genre = "";
    private String added_series = "";
    private String added_title = "";
    private String added_author = "";
    public static final String ADDED_HAS_INFO = "ADDED_HAS_INFO";
    public static final String ADDED_GENRE = "ADDED_GENRE";
    public static final String ADDED_SERIES = "ADDED_SERIES";
    public static final String ADDED_TITLE = "ADDED_TITLE";
    public static final String ADDED_AUTHOR = "ADDED_AUTHOR";

    /**
     * Standardize the names of tabs ANY NEW NAMES NEED TO BE ADDED TO
     * mTabNames, below
     */
    private static String TAB_NAME_EDIT_BOOK = "edit_book";
    private static String TAB_NAME_EDIT_NOTES = "edit_book_notes";
    private static String TAB_NAME_EDIT_FRIENDS = "edit_book_friends";
    private static String TAB_NAME_EDIT_ANTHOLOGY = "edit_book_anthology";

    /** Key using in intent to start this class in read-only mode */
    public static final String KEY_READ_ONLY = "key_read_only";

    //public int mCurrentTab = 0;
    private long mRowId;
    private CatalogueDBAdapter mDbHelper = new CatalogueDBAdapter(this);
    private Tab mAnthologyTab = null;
    private BookData mBookData;
    private boolean mIsReadOnly;

    private Button mConfirmButton;
    private Button mCancelButton;

    public void onCreate(Bundle savedInstanceState) {
        Tracker.enterOnCreate(this);
        super.onCreate(savedInstanceState);

        setContentView(R.layout.book_edit_base);

        final ActionBar actionBar = getSupportActionBar();

        // Get the extras; we use them a lot
        Bundle extras = getIntent().getExtras();

        // We need the row ID
        {
            Long rowId = savedInstanceState != null ? savedInstanceState.getLong(CatalogueDBAdapter.KEY_ROWID)
                    : null;
            if (rowId == null) {
                rowId = extras != null ? extras.getLong(CatalogueDBAdapter.KEY_ROWID) : null;
            }
            if (rowId == null) {
                mRowId = 0;
            } else {
                mRowId = rowId;
            }
        }

        // Various functions depend on read-only state
        if (extras != null && extras.containsKey(KEY_READ_ONLY) && extras.getBoolean(KEY_READ_ONLY)) {
            mIsReadOnly = (mRowId != 0);
        } else {
            mIsReadOnly = false;
        }

        mDbHelper.open();

        // Get the book data from the bundle or the database
        loadBookData(mRowId, savedInstanceState == null ? extras : savedInstanceState);

        // get the passed parameters
        int tabIndex;
        if (savedInstanceState != null && savedInstanceState.containsKey(BookEdit.TAB)) {
            tabIndex = savedInstanceState.getInt(BookEdit.TAB);
        } else if (extras != null && extras.containsKey(BookEdit.TAB)) {
            tabIndex = extras.getInt(BookEdit.TAB);
        } else {
            tabIndex = 0;
        }

        int anthology_num = 0;
        if (mBookData.getRowId() > 0) {
            anthology_num = mBookData.getInt(BookData.KEY_ANTHOLOGY);
        }

        // Class needed for the first tab: BookEditFields except when book is
        // exist and read-only mode enabled
        if (mIsReadOnly) {
            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);

            BookDetailsReadOnly details = new BookDetailsReadOnly();
            details.setArguments(getIntent().getExtras());
            getSupportFragmentManager().beginTransaction().replace(R.id.fragment, details).commit();
        } else {
            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
            initTab(actionBar, new TabListener<BookEditFields>(this, TAB_NAME_EDIT_BOOK, BookEditFields.class),
                    R.string.details, R.drawable.ic_tab_edit, extras);
            initTab(actionBar, new TabListener<BookEditNotes>(this, TAB_NAME_EDIT_NOTES, BookEditNotes.class),
                    R.string.notes, R.drawable.ic_tab_notes, extras);
            // Only show the other tabs if it is not new book, otherwise only
            // show the first tab
            if (mRowId > 0) {
                initTab(actionBar,
                        new TabListener<BookEditLoaned>(this, TAB_NAME_EDIT_FRIENDS, BookEditLoaned.class),
                        R.string.loan, R.drawable.ic_tab_friends, extras);

                // Only show the anthology tab if the book is marked as an
                // anthology
                if (anthology_num != 0) {
                    mAnthologyTab = initTab(actionBar,
                            new TabListener<BookEditAnthology>(this, TAB_NAME_EDIT_ANTHOLOGY,
                                    BookEditAnthology.class),
                            R.string.edit_book_anthology, R.drawable.ic_tab_anthology, extras);
                }
            }
            actionBar.setSelectedNavigationItem(tabIndex);
        }

        if (mIsReadOnly) {
            findViewById(R.id.buttons).setVisibility(View.GONE);
        } else {
            findViewById(R.id.buttons).setVisibility(View.VISIBLE);
        }

        mConfirmButton = (Button) findViewById(R.id.confirm);
        mCancelButton = (Button) findViewById(R.id.cancel);

        // mConfirmButton.setOnClickListener - This is set in populate fields.
        // The behaviour changes depending on if it is adding or saving
        mCancelButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                // Cleanup because we may have made global changes
                mDbHelper.purgeAuthors();
                mDbHelper.purgeSeries();
                // We're done.
                setResult(Activity.RESULT_OK);

                if (isDirty()) {
                    StandardDialogs.showConfirmUnsavedEditsDialog(BookEdit.this, null);
                } else {
                    finish();
                }
            }
        });

        mConfirmButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                saveState(new DoConfirmAction());
            }
        });

        if (mRowId > 0) {
            mConfirmButton.setText(R.string.confirm_save);
        } else {
            mConfirmButton.setText(R.string.confirm_add);
        }

        initBooklist(extras, savedInstanceState);

        // Must come after all book data and list retrieved.
        setActivityTitle();

        // Setup the background
        Utils.initBackground(R.drawable.bc_background_gradient_dim, this, false);

        Tracker.exitOnCreate(this);

    }

    /**
     * If we are passed a flatened book list, get it and validate it
     * 
     * @param extras
     * @param savedInstanceState
     */
    private void initBooklist(Bundle extras, Bundle savedInstanceState) {
        if (extras != null) {
            String list = extras.getString("FlattenedBooklist");
            if (list != null && !list.equals("")) {
                mList = new FlattenedBooklist(mDbHelper.getDb(), list);
                // Check to see it really exists. The underlying table
                // disappeared once in testing
                // which is hard to explain; it theoretically should only happen
                // if the app closes
                // the database or if the activity pauses with 'isFinishing()'
                // returning true.
                if (mList.exists()) {
                    int pos;
                    if (savedInstanceState != null && savedInstanceState.containsKey("FlattenedBooklistPosition")) {
                        pos = savedInstanceState.getInt("FlattenedBooklistPosition");
                    } else if (extras.containsKey("FlattenedBooklistPosition")) {
                        pos = extras.getInt("FlattenedBooklistPosition");
                    } else {
                        pos = 0;
                    }
                    mList.moveTo(pos);
                    while (!mList.getBookId().equals(mRowId)) {
                        if (!mList.moveNext())
                            break;
                    }
                    if (!mList.getBookId().equals(mRowId)) {
                        mList.close();
                        mList = null;
                    } else {
                        // Add a gesture lister for 'swipe' gestures
                        mGestureDetector = new GestureDetector(this, mGestureListener);
                    }

                } else {
                    mList.close();
                    mList = null;
                }
            }
        }
    }

    @Override
    /**
     * We override the dispatcher because the ScrollView will consume
     * all events otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mGestureDetector != null && mGestureDetector.onTouchEvent(event))
            return true;
        super.dispatchTouchEvent(event);
        // Always return true; we want the events.
        return true;
    }

    /**
     * Listener to handle 'fling' events; we could handle others but need to be
     * careful about possible clicks and scrolling.
     */
    GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (mList == null)
                return false;

            // Make sure we have considerably more X-velocity than Y-velocity;
            // otherwise it might be a scroll.
            if (Math.abs(velocityX / velocityY) > 2) {
                boolean moved;
                // Work out which way to move, and do it.
                if (velocityX > 0) {
                    moved = mList.movePrev();
                } else {
                    moved = mList.moveNext();
                }
                if (moved) {
                    setRowId(mList.getBookId());
                }
                return true;
            } else {
                return false;
            }
        }
    };

    /**
     * This function will populate the forms elements in three different ways 1.
     * If a valid rowId exists it will populate the fields from the database 2.
     * If fields have been passed from another activity (e.g. ISBNSearch) it
     * will populate the fields from the bundle 3. It will leave the fields
     * blank for new books.
     */
    private void loadBookData(Long rowId, Bundle bestBundle) {
        if (bestBundle != null && bestBundle.containsKey("bookData")) {
            // If we have saved book data, use it
            mBookData = new BookData(rowId, bestBundle.getBundle("bookData"));
        } else {
            // Just load based on rowId
            mBookData = new BookData(rowId);
        }
    }

    /**
     * This is a straight passthrough
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        // 1. the call to duplicateBook() no longer uses this ID
        // 2. We can't just finish(); there might be unsaved edits.
        // 3. if we want to finish on creating a new book, we should do it when
        // we start the activity
        // switch (requestCode) {
        // setResult(resultCode, intent);
        // finish();
        // break;
        // }
    }

    @Override
    protected void onDestroy() {
        Tracker.enterOnDestroy(this);
        super.onDestroy();
        mDbHelper.close();
        Tracker.exitOnDestroy(this);
    }

    @Override
    /**
     * Close the list object (frees statements) and if we are finishing, delete the temp table.
     * 
     * This is an ESSENTIAL step; for some reason, in Android 2.1 if these statements are not
     * cleaned up, then the underlying SQLiteDatabase gets double-dereferenced, resulting in
     * the database being closed by the deeply dodgy auto-close code in Android.
     */
    public void onPause() {
        if (mList != null) {
            mList.close();
            if (this.isFinishing()) {
                mList.deleteData();
            }
        }
        super.onPause();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        Tracker.enterOnSaveInstanceState(this);
        super.onSaveInstanceState(outState);

        ActionBar actionBar = this.getSupportActionBar();

        outState.putLong(CatalogueDBAdapter.KEY_ROWID, mRowId);
        outState.putBundle("bookData", mBookData.getRawData());
        if (mList != null) {
            outState.putInt("FlattenedBooklistPosition", (int) mList.getPosition());
        }
        outState.putInt(BookEdit.TAB, actionBar.getSelectedNavigationIndex());
        Tracker.exitOnSaveInstanceState(this);
    }

    /********************************************************
     * Standard STATIC Methods
     * ******************************************************
     */

    /**
     * Open book for viewing in edit or read-only mode. The mode depends on
     * {@link BookCataloguePreferences#PREF_OPEN_BOOK_READ_ONLY} preference
     * option. If it set book opened in read-only mode otherwise in edit mode
     * (default).
     * 
     * @param a
     *            current activity from which we start
     * @param id
     *            The id of the book to view
     * @param builder
     *            (Optional) builder for underlying book list. Only used in
     *            read-only view.
     * @param position
     *            (Optional) position in underlying book list. Only used in
     *            read-only view.
     */
    public static void openBook(Activity a, long id, BooklistBuilder builder, Integer position) {
        boolean isReadOnly = BookCatalogueApp.getAppPreferences()
                .getBoolean(BookCataloguePreferences.PREF_OPEN_BOOK_READ_ONLY, true);
        if (isReadOnly) {
            // Make a flattened copy of the list of books, if available
            String listTable = null;
            if (builder != null) {
                listTable = builder.createFlattenedBooklist().getTable().getName();
            }
            BookEdit.viewBook(a, id, listTable, position);
        } else {
            BookEdit.editBook(a, id, BookEdit.TAB_EDIT);
        }
    }

    /**
     * Load the EditBook activity based on the provided id in edit mode. Also
     * open to the provided tab.
     * 
     * @param id
     *            The id of the book to edit
     * @param tab
     *            Which tab to open first
     */
    public static void editBook(Activity a, long id, int tab) {
        Intent i = new Intent(a, BookEdit.class);
        i.putExtra(CatalogueDBAdapter.KEY_ROWID, id);
        i.putExtra(BookEdit.TAB, tab);
        a.startActivityForResult(i, UniqueId.ACTIVITY_EDIT_BOOK);
        return;
    }

    /**
     * Load the EditBook tab activity in read-only mode. The first tab is book
     * details.
     * 
     * @param a
     *            current activity from which we start
     * @param id
     *            The id of the book to view
     * @param listTable
     *            (Optional) name of the temp table comtaining a list of book
     *            IDs.
     * @param position
     *            (Optional) position in underlying book list. Only used in
     *            read-only view.
     */
    public static void viewBook(Activity a, long id, String listTable, Integer position) {
        Intent i = new Intent(a, BookEdit.class);
        i.putExtra("FlattenedBooklist", listTable);
        if (position != null) {
            i.putExtra("FlattenedBooklistPosition", position);
        }
        i.putExtra(CatalogueDBAdapter.KEY_ROWID, id);
        i.putExtra(BookEdit.TAB, BookEdit.TAB_EDIT); // needed extra for
        // creating BookEdit
        i.putExtra(BookEdit.KEY_READ_ONLY, true);
        a.startActivityForResult(i, UniqueId.ACTIVITY_VIEW_BOOK);
        return;
    }

    /**
     * Initialize a TabSpec according to defined parameters and add it to the
     * TabHost.
     * 
     * @param tabHost
     *            parent TabHost
     * @param intentClass
     *            class for specifying intent. It`s the Activity class contained
     *            in this tab.
     * @param tabTag
     *            required tag of tab
     * @param titleResId
     *            resource id of a title of the tab
     * @param iconResId
     *            resource id of an icon (drawable) of the tab
     * @param extras
     *            extras for putting in the intent. If extras is null they will
     *            not be added.
     */
    private <T extends BookCatalogueFragment> Tab initTab(ActionBar actionBar, TabListener<T> listener,
            int titleResId, int iconResId, Bundle extras) {

        Resources resources = getResources();
        String tabTitle = resources.getString(titleResId);

        Tab tab = actionBar.newTab().setText(tabTitle).setTabListener(listener).setTag(titleResId);
        // tab.setIcon(iconResId);

        actionBar.addTab(tab);
        return tab;
    }

    /**
     * Mark the data as dirty (or not)
     */
    public void setDirty(boolean dirty) {
        mIsDirtyFlg = dirty;
    }

    /**
     * Get the current status of the data in this activity
     */
    public boolean isDirty() {
        return mIsDirtyFlg;
    }

    /**
     * If 'back' is pressed, and the user has made changes, ask them if they
     * really want to lose the changes.
     * 
     * We don't use onBackPressed because it does not work with API level 4.
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (isDirty()) {
                StandardDialogs.showConfirmUnsavedEditsDialog(this, null);
            } else {
                doFinish();
            }
            return true;
        } else {
            return super.onKeyDown(keyCode, event);
        }
    }

    /**
     * Check if edits need saving, and finish the activity if not
     */
    private void doFinish() {
        if (isDirty()) {
            StandardDialogs.showConfirmUnsavedEditsDialog(this, new Runnable() {
                @Override
                public void run() {
                    finishAndSendIntent();
                }
            });
        } else {
            finishAndSendIntent();
        }
    }

    /**
     * Actually finish this activity making sure an intent is returned.
     */
    private void finishAndSendIntent() {
        Intent i = new Intent();
        i.putExtra(CatalogueDBAdapter.KEY_ROWID, mBookData.getRowId());
        setResult(Activity.RESULT_OK, i);
        finish();
    }

    /**
     * Show or hide the anthology tab
     */
    @Override
    public void setShowAnthology(boolean showAnthology) {
        ActionBar actionBar = this.getSupportActionBar();
        if (showAnthology) {
            if (mAnthologyTab == null) {
                mAnthologyTab = initTab(actionBar,
                        new TabListener<BookEditAnthology>(this, TAB_NAME_EDIT_ANTHOLOGY, BookEditAnthology.class),
                        R.string.edit_book_anthology, R.drawable.ic_tab_anthology, getIntent().getExtras());
            }
        } else {
            if (mAnthologyTab != null) {
                actionBar.removeTab(mAnthologyTab);
                mAnthologyTab = null;
            }
        }

    }

    /**
     * Listener to create/show the relevant tabs
     * 
     * @author pjw
     *
     * @param <T>      Fragment type
     */
    public static class TabListener<T extends BookCatalogueFragment> implements ActionBar.TabListener {
        private Fragment mFragment;
        private final Activity mActivity;
        private final String mTag;
        private final Class<T> mClass;

        /**
         * Constructor used each time a new tab is created.
         * 
         * @param activity
         *            The host Activity, used to instantiate the fragment
         * @param tag
         *            The identifier tag for the fragment
         * @param clz
         *            The fragment's Class, used to instantiate the fragment
         */
        public TabListener(Activity activity, String tag, Class<T> clz) {
            mActivity = activity;
            mTag = tag;
            mClass = clz;
        }

        /* The following are each of the ActionBar.TabListener callbacks */

        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            // Check if the fragment is already initialized
            if (mFragment == null) {
                // If not, instantiate and add it to the activity
                mFragment = T.instantiate(mActivity, mClass.getName());
                // ft.add(android.R.id.content, mFragment, mTag);
                ft.replace(R.id.fragment, mFragment, mTag);
            } else {
                // If it exists, simply attach it in order to show it
                ft.attach(mFragment);
            }
        }

        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
            if (mFragment != null) {
                // Detach the fragment, because another one is being attached
                ft.detach(mFragment);
            }
        }

        public void onTabReselected(Tab tab, FragmentTransaction ft) {
            // User selected the already selected tab. Usually do nothing.
        }
    }

    @Override
    public BookData getBookData() {
        return mBookData;
    }

    @Override
    public void setRowId(Long id) {
        if (mRowId != id) {
            mRowId = id;
            loadBookData(id, null);
            Fragment frag = getSupportFragmentManager().findFragmentById(R.id.fragment);
            if (frag instanceof DataEditor) {
                ((DataEditor) frag).reloadData(mBookData);
            }
            setActivityTitle();
        }
    }

    /**
     * Validate the current data in all fields that have validators. Display any
     * errors.
     * 
     * @param values
     *            The values to use
     * 
     * @return Boolean success or failure.
     */
    private boolean validate() {
        Fragment frag = getSupportFragmentManager().findFragmentById(R.id.fragment);
        if (frag instanceof DataEditor) {
            ((DataEditor) frag).saveAllEdits(mBookData);
        }
        if (!mBookData.validate()) {
            Toast.makeText(this, mBookData.getValidationExceptionMessage(getResources()), Toast.LENGTH_LONG).show();
            return false;
        }
        return true;
    }

    /**
     * This will save a book into the database, by either updating or created a
     * book. Minor modifications will be made to the strings: - Titles will be
     * rewords so 'a', 'the', 'an' will be moved to the end of the string (this
     * is only done for NEW books)
     * 
     * - Date published will be converted from a date to a string
     * 
     * Thumbnails will also be saved to the correct location
     * 
     * It will check if the book already exists (isbn search) if you are
     * creating a book; if so the user will be prompted to confirm.
     * 
     * In all cases, once the book is added/created, or not, the appropriate
     * method of the passed nextStep parameter will be executed. Passing
     * nextStep is necessary because this method may return after displaying a
     * dialogue.
     * 
     * @param nextStep
     *            The next step to be executed on success/failure.
     * 
     * @throws IOException
     */
    private void saveState(final PostSaveAction nextStep) {
        // Ignore validation failures; we still validate to get the current
        // values.
        if (!validate()) {
            // nextStep.failure();
            // return;
        }

        // However, there is some data that we really do require...
        if (mBookData.getAuthorList().size() == 0) {
            Toast.makeText(this, getResources().getText(R.string.author_required), Toast.LENGTH_LONG).show();
            return;
        }
        if (!mBookData.containsKey(CatalogueDBAdapter.KEY_TITLE)
                || mBookData.getString(CatalogueDBAdapter.KEY_TITLE).trim().length() == 0) {
            Toast.makeText(this, getResources().getText(R.string.title_required), Toast.LENGTH_LONG).show();
            return;
        }

        if (mRowId == 0) {
            String isbn = mBookData.getString(CatalogueDBAdapter.KEY_ISBN);
            /* Check if the book currently exists */
            if (!isbn.equals("")) {
                if (mDbHelper.checkIsbnExists(isbn, true)) {
                    /*
                     * If it exists, show a dialog and use it to perform the
                     * next action, according to the users choice.
                     */
                    SaveAlert alert = new SaveAlert();
                    alert.setMessage(getResources().getString(R.string.duplicate_book_message));
                    alert.setTitle(R.string.duplicate_book_title);
                    alert.setIcon(android.R.drawable.ic_menu_info_details);
                    alert.setButton2(this.getResources().getString(R.string.ok),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int which) {
                                    updateOrCreate();
                                    nextStep.success();
                                    return;
                                }
                            });
                    alert.setButton(this.getResources().getString(R.string.cancel),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int which) {
                                    nextStep.failure();
                                    return;
                                }
                            });
                    alert.show();
                    return;
                }
            }
        }

        // No special actions required...just do it.
        updateOrCreate();
        nextStep.success();
        return;
    }

    private class SaveAlert extends AlertDialog {

        protected SaveAlert() {
            super(BookEdit.this);
        }
    }

    /**
     * Save the collected book details
     */
    private void updateOrCreate() {
        if (mRowId == 0) {
            long id = mDbHelper.createBook(mBookData, 0);

            if (id > 0) {
                setRowId(id);
                File thumb = CatalogueDBAdapter.getTempThumbnail();
                File real = CatalogueDBAdapter.fetchThumbnailByUuid(mDbHelper.getBookUuid(mRowId));
                thumb.renameTo(real);
            }
        } else {
            mDbHelper.updateBook(mRowId, mBookData, 0);
        }

        /*
         * These are global variables that will be sent via intent back to the
         * list view, if added/created
         */
        try {
            ArrayList<Author> authors = mBookData.getAuthorList();
            if (authors.size() > 0) {
                added_author = authors.get(0).getSortName();
            } else {
                added_author = "";
            }
        } catch (Exception e) {
            Logger.logError(e);
        }
        ;
        try {
            ArrayList<Series> series = mBookData.getSeriesList();
            if (series.size() > 0)
                added_series = series.get(0).name;
            else
                added_series = "";
        } catch (Exception e) {
            Logger.logError(e);
        }
        ;

        added_title = mBookData.getString(CatalogueDBAdapter.KEY_TITLE);
        added_genre = mBookData.getString(CatalogueDBAdapter.KEY_GENRE);
    }

    public interface PostSaveAction {
        public void success();

        public void failure();
    }

    private class DoConfirmAction implements PostSaveAction {

        DoConfirmAction() {
        }

        public void success() {
            Intent i = new Intent();
            i.putExtra(CatalogueDBAdapter.KEY_ROWID, mBookData.getRowId());
            i.putExtra(ADDED_HAS_INFO, true);
            i.putExtra(ADDED_GENRE, added_genre);
            i.putExtra(ADDED_SERIES, added_series);
            i.putExtra(ADDED_TITLE, added_title);
            i.putExtra(ADDED_AUTHOR, added_author);

            setResult(Activity.RESULT_OK, i);
            finish();
        }

        public void failure() {
            // Do nothing
        }
    }

    /**
     * Sets title of the parent activity in the next format:<br>
     * <i>"title"</i>
     * 
     * @param title
     */
    private void setActivityTitle() {
        ActionBar bar = this.getSupportActionBar();
        if (mIsReadOnly && mList != null) {
            bar.setTitle(mBookData.getString(CatalogueDBAdapter.KEY_TITLE));
            bar.setSubtitle(
                    mBookData.getAuthorTextShort() + " (" + String.format(getResources().getString(R.string.x_of_y),
                            mList.getAbsolutePosition(), mList.getCount()) + ")");
        } else if (mBookData.getRowId() > 0) {
            bar.setTitle(mBookData.getString(CatalogueDBAdapter.KEY_TITLE));
            bar.setSubtitle(mBookData.getAuthorTextShort());
        } else {
            bar.setTitle(this.getResources().getString(R.string.menu_insert));
            bar.setSubtitle(null);
        }
    }

    private ArrayList<String> mPublishers;

    /**
     * Load a publisher list; reloading this list every time a tab changes is
     * slow. So we cache it.
     * 
     * @return List of publishers
     */
    public ArrayList<String> getPublishers() {
        if (mPublishers == null) {
            mPublishers = new ArrayList<String>();
            Cursor publisher_cur = mDbHelper.fetchAllPublishers();
            final int col = publisher_cur.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_PUBLISHER);
            try {
                while (publisher_cur.moveToNext()) {
                    mPublishers.add(publisher_cur.getString(col));
                }
            } finally {
                publisher_cur.close();
            }
        }
        return mPublishers;
    }

    private ArrayList<String> mGenres;

    /**
     * Load a genre list; reloading this list every time a tab changes is slow.
     * So we cache it.
     * 
     * @return List of publishers
     */
    public ArrayList<String> getGenres() {
        if (mGenres == null) {
            mGenres = new ArrayList<String>();
            Cursor genre_cur = mDbHelper.fetchAllGenres("");
            final int col = genre_cur.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_ROWID);
            try {
                while (genre_cur.moveToNext()) {
                    mGenres.add(genre_cur.getString(col));
                }
            } finally {
                genre_cur.close();
            }
        }
        return mGenres;
    }

    /** List of languages in database so far */
    private ArrayList<String> mLanguages;

    /**
     * Load a language list; reloading this list every time a tab changes is slow.
     * So we cache it.
     * 
     * @return List of languages
     */
    @Override
    public ArrayList<String> getLanguages() {
        if (mLanguages == null) {
            mLanguages = new ArrayList<String>();
            Cursor cur = mDbHelper.fetchAllLanguages("");
            final int col = cur.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_ROWID);
            try {
                while (cur.moveToNext()) {
                    String s = cur.getString(col);
                    if (s != null && !s.equals("")) {
                        mLanguages.add(cur.getString(col));
                    }
                }
            } finally {
                cur.close();
            }
        }
        return mLanguages;
    }

    private ArrayList<String> mFormats;

    /**
     * Load a format list; reloading this list every time a tab changes is slow.
     * So we cache it.
     * 
     * @return List of publishers
     */
    public ArrayList<String> getFormats() {
        if (mFormats == null) {
            mFormats = mDbHelper.getFormats();
        }
        return mFormats;
    }

    /**
     * Dialog handler; pass results to relevant destination
     */
    @Override
    public void onPartialDatePickerSet(int dialogId, PartialDatePickerFragment dialog, Integer year, Integer month,
            Integer day) {
        Fragment frag = getSupportFragmentManager().findFragmentById(R.id.fragment);
        if (frag instanceof OnPartialDatePickerListener) {
            ((OnPartialDatePickerListener) frag).onPartialDatePickerSet(dialogId, dialog, year, month, day);
        } else {
            Toast.makeText(this, R.string.unexpected_error, Toast.LENGTH_LONG).show();
            Logger.logError(new RuntimeException("Received date dialog result with no fragment to handle it"));
        }
        // Make sure it's dismissed
        if (dialog.isVisible())
            dialog.dismiss();
    }

    /**
     * Dialog handler; pass results to relevant destination
     */
    @Override
    public void onPartialDatePickerCancel(int dialogId, PartialDatePickerFragment dialog) {
        Fragment frag = getSupportFragmentManager().findFragmentById(R.id.fragment);
        if (frag instanceof OnPartialDatePickerListener) {
            ((OnPartialDatePickerListener) frag).onPartialDatePickerCancel(dialogId, dialog);
        } else {
            Toast.makeText(this, R.string.unexpected_error, Toast.LENGTH_LONG).show();
            Logger.logError(
                    new RuntimeException("Received date dialog cancellation with no fragment to handle it"));
        }
        // Make sure it's dismissed
        if (dialog.isVisible())
            dialog.dismiss();

    }

    /**
     * Dialog handler; pass results to relevant destination
     */
    @Override
    public void onTextFieldEditorSave(int dialogId, TextFieldEditorFragment dialog, String newText) {
        Fragment frag = getSupportFragmentManager().findFragmentById(R.id.fragment);
        if (frag instanceof OnTextFieldEditorListener) {
            ((OnTextFieldEditorListener) frag).onTextFieldEditorSave(dialogId, dialog, newText);
        } else {
            Toast.makeText(this, R.string.unexpected_error, Toast.LENGTH_LONG).show();
            Logger.logError(
                    new RuntimeException("Received onTextFieldEditorSave result with no fragment to handle it"));
        }
        // Make sure it's dismissed
        if (dialog.isVisible())
            dialog.dismiss();
    }

    /**
     * Dialog handler; pass results to relevant destination
     */
    @Override
    public void onTextFieldEditorCancel(int dialogId, TextFieldEditorFragment dialog) {
        Fragment frag = getSupportFragmentManager().findFragmentById(R.id.fragment);
        if (frag instanceof OnTextFieldEditorListener) {
            ((OnTextFieldEditorListener) frag).onTextFieldEditorCancel(dialogId, dialog);
        } else {
            Toast.makeText(this, R.string.unexpected_error, Toast.LENGTH_LONG).show();
            Logger.logError(
                    new RuntimeException("Received onTextFieldEditorCancel result with no fragment to handle it"));
        }
        // Make sure it's dismissed
        if (dialog.isVisible())
            dialog.dismiss();
    }

    /**
     * Dialog handler; pass results to relevant destination
     */
    @Override
    public void onBookshelfCheckChanged(int dialogId, BookshelfDialogFragment dialog, boolean checked, String shelf,
            String textList, String encodedList) {

        Fragment frag = getSupportFragmentManager().findFragmentById(R.id.fragment);
        if (frag instanceof OnBookshelfCheckChangeListener) {
            ((OnBookshelfCheckChangeListener) frag).onBookshelfCheckChanged(dialogId, dialog, checked, shelf,
                    textList, encodedList);
        } else {
            Toast.makeText(this, R.string.unexpected_error, Toast.LENGTH_LONG).show();
            Logger.logError(
                    new RuntimeException("Received onBookshelfCheckChanged result with no fragment to handle it"));
        }
    }

    /**
     * menu handler; handle the 'home' key, otherwise, pass on the event
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {

        case android.R.id.home:
            doFinish();
            return true;

        default:
            return super.onOptionsItemSelected(item);
        }

    }

}