/* * @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.util.ArrayList; import java.util.Iterator; import android.app.Activity; import android.content.Intent; import android.database.SQLException; import android.os.Bundle; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.eleybourn.bookcatalogue.Fields.Field; 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.StandardDialogs.SimpleDialogItem; import com.eleybourn.bookcatalogue.dialogs.StandardDialogs.SimpleDialogOnClickListener; 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; public class BookEditFields extends BookDetailsAbstract implements OnPartialDatePickerListener, OnTextFieldEditorListener, OnBookshelfCheckChangeListener { /** * Class to implement a clickable span of text and call a listener when text is clicked. */ static class InternalSpan extends ClickableSpan { OnClickListener mListener; public InternalSpan(OnClickListener listener) { mListener = listener; } @Override public void onClick(View widget) { mListener.onClick(widget); } } public static final int ACTIVITY_EDIT_AUTHORS = 1000; public static final int ACTIVITY_EDIT_SERIES = 1001; private static final int DATE_DIALOG_ID = 1; private static final int DESCRIPTION_DIALOG_ID = 3; /** * Display the edit fields page */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.edit_book, container, false); } public void onActivityCreated(Bundle savedInstanceState) { Tracker.enterOnActivityCreated(this); double t0 = System.currentTimeMillis(); double t1 = 0; double t2 = 0; try { t1 = System.currentTimeMillis(); super.onActivityCreated(savedInstanceState); t2 = System.currentTimeMillis(); if (savedInstanceState != null) { mEditManager.setDirty(savedInstanceState.getBoolean("Dirty")); } //Set click listener on Author field View v = getView().findViewById(R.id.author); //Reusable view for setting listeners v.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(getActivity(), EditAuthorList.class); i.putExtra(CatalogueDBAdapter.KEY_AUTHOR_ARRAY, mEditManager.getBookData().getAuthorList()); i.putExtra(CatalogueDBAdapter.KEY_ROWID, mEditManager.getBookData().getRowId()); i.putExtra("title_label", CatalogueDBAdapter.KEY_TITLE); i.putExtra("title", mFields.getField(R.id.title).getValue().toString()); startActivityForResult(i, ACTIVITY_EDIT_AUTHORS); } }); //Set click listener on Series field v = getView().findViewById(R.id.series); v.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(getActivity(), EditSeriesList.class); i.putExtra(CatalogueDBAdapter.KEY_SERIES_ARRAY, mEditManager.getBookData().getSeriesList()); i.putExtra(CatalogueDBAdapter.KEY_ROWID, mEditManager.getBookData().getRowId()); i.putExtra("title_label", CatalogueDBAdapter.KEY_TITLE); i.putExtra("title", mFields.getField(R.id.title).getValue().toString()); startActivityForResult(i, ACTIVITY_EDIT_SERIES); } }); ArrayAdapter<String> publisher_adapter = new ArrayAdapter<String>(this.getActivity(), android.R.layout.simple_dropdown_item_1line, mEditManager.getPublishers()); mFields.setAdapter(R.id.publisher, publisher_adapter); ArrayAdapter<String> genre_adapter = new ArrayAdapter<String>(this.getActivity(), android.R.layout.simple_dropdown_item_1line, mEditManager.getGenres()); mFields.setAdapter(R.id.genre, genre_adapter); ArrayAdapter<String> language_adapter = new ArrayAdapter<String>(this.getActivity(), android.R.layout.simple_dropdown_item_1line, mEditManager.getLanguages()); mFields.setAdapter(R.id.language, language_adapter); mFields.setListener(R.id.date_published, new View.OnClickListener() { public void onClick(View view) { showDatePublishedDialog(); } }); final Field formatField = mFields.getField(R.id.format); // Get the formats to use in the AutoComplete stuff AutoCompleteTextView formatText = (AutoCompleteTextView) formatField.getView(); formatText.setAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_dropdown_item_1line, mEditManager.getFormats())); // Get the drop-down button for the formats list and setup dialog ImageView formatButton = (ImageView) getView().findViewById(R.id.format_button); formatButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { StandardDialogs.selectStringDialog(getActivity().getLayoutInflater(), getString(R.string.format), mDbHelper.getFormats(), formatField.getValue().toString(), new SimpleDialogOnClickListener() { @Override public void onClick(SimpleDialogItem item) { formatField.setValue(item.toString()); } }); } }); //Spinner formatSpinner = (Spinner) formatField.getView(); //ArrayAdapter<String> formatArr = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item); //formatArr.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); //formatSpinner.setAdapter(formatArr); //formatArr.add(getString(R.string.format1)); //formatArr.add(getString(R.string.format2)); //formatArr.add(getString(R.string.format3)); //formatArr.add(getString(R.string.format4)); //formatArr.add(getString(R.string.format5)); Field bookshelfButtonFe = mFields.getField(R.id.bookshelf); bookshelfButtonFe.getView().setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Field f = mFields.getField(R.id.bookshelf); BookshelfDialogFragment frag = BookshelfDialogFragment.newInstance( R.id.bookshelf, mEditManager.getBookData().getRowId(), mEditManager.getBookData().getBookshelfText(), mEditManager.getBookData().getBookshelfList() ); frag.show(getFragmentManager(),"bookshelves_dialog"); } }); // Build the label for the book description if this is first time, otherwise will be built later if (savedInstanceState == null) buildDescription(); // Setup the Save/Add/Anthology UI elements setupUi(); try { Utils.fixFocusSettings(getView()); getView().findViewById(R.id.author).requestFocus(); } catch (Exception e) { // Log, but ignore. This is a non-critical feature that prevents crashes when the // 'next' key is pressed and some views have been hidden. Logger.logError(e); } if (savedInstanceState != null) { mEditManager.setDirty(savedInstanceState.getBoolean("Dirty")); } else { mEditManager.setDirty(false); } } catch (IndexOutOfBoundsException e) { Logger.logError(e); } catch (SQLException e) { Logger.logError(e); } finally { Tracker.exitOnActivityCreated(this); } double tn = System.currentTimeMillis(); System.out.println("BEF oAC(super): " + (t2 - t1)); System.out.println("BEF oAC: " + (tn - t0)); } private void showDescriptionDialog() { Object o = mFields.getField(R.id.description).getValue(); String description = (o == null? null : o.toString()); TextFieldEditorFragment dlg = TextFieldEditorFragment.newInstance(R.id.description, R.string.description, description); dlg.show(getFragmentManager(), null); } private void showDatePublishedDialog() { PartialDatePickerFragment frag = PartialDatePickerFragment.newInstance(); Utils.prepareDateDialogFragment(frag, mFields.getField(R.id.date_published).getValue()); frag.setTitle(R.string.date_published); frag.setDialogId(R.id.date_published); // Set to the destination field ID frag.show(getFragmentManager(), null); } /** * 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 populateFields() { double t0 = System.currentTimeMillis(); Bundle extras = getActivity().getIntent().getExtras(); final BookData book = mEditManager.getBookData(); populateFieldsFromBook(book); if (book.getRowId() > 0) { //Populating from database // populateFieldsFromBook(book); //getActivity().setTitle(this.getResources().getString(R.string.menu)); } else { if (extras != null) { // From the ISBN Search (add) try { if (extras.containsKey("book")) { throw new RuntimeException("[book] array passed in Intent"); } else { Bundle values = (Bundle)extras.getParcelable("bookData"); Iterator<Fields.Field> i = mFields.iterator(); while(i.hasNext()) { Fields.Field f = i.next(); if (!f.column.equals("") && values.containsKey(f.column)) { try { f.setValue(values.getString(f.column)); } catch (Exception e) { String msg = "Populate field " + f.column + " failed: " + e.getMessage(); Logger.logError(e, msg); } } } } } catch (NullPointerException e) { Logger.logError(e); } } initDefaultShelf(); setCoverImage(); } populateAuthorListField(); populateSeriesListField(); // Restore default visibility and hide unused/unwanted and empty fields showHideFields(false); System.out.println("BEF popF: " + (System.currentTimeMillis() - t0)); } /** * Use the currently selected bookshelf as default */ private void initDefaultShelf() { final BookData book = mEditManager.getBookData(); final String list = book.getBookshelfList(); if (list == null || list.equals("")) { String currShelf = BookCatalogueApp.getAppPreferences().getString(BooksOnBookshelf.PREF_BOOKSHELF, ""); if (currShelf.equals("")) { currShelf = mDbHelper.getBookshelfName(1); } String encoded_shelf = Utils.encodeListItem(currShelf, BOOKSHELF_SEPERATOR); Field fe = mFields.getField(R.id.bookshelf); fe.setValue(currShelf); book.setBookshelfList(encoded_shelf); } } private void setupUi() { final CheckBox cb = (CheckBox)getView().findViewById(R.id.anthology); cb.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { mEditManager.setShowAnthology(cb.isChecked()); //saveState(new DoAnthologyAction(cb.isChecked())); } }); } /** * Setup the 'description' header field to have a clickable link. */ private void buildDescription() { double t0 = System.currentTimeMillis(); // get the view final TextView tv = (TextView)getView().findViewById(R.id.descriptionLabel); // Build the prefic text ('Description ') String baseText = getString(R.string.description) + " "; // Create the span ('Description (edit...)'). SpannableString f = new SpannableString(baseText + "(" + getString(R.string.edit_lc_ellipsis) + ")"); f.setSpan(new InternalSpan(new OnClickListener() { public void onClick(View v) { BookEditFields.this.showDescriptionDialog(); } }), baseText.length(), f.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // Quirks in Android mean old spans may be preserved; delete them clearOldInternalSpans(tv); // Set the text tv.setText(f); // Set the MovementMethod to allow clicks tv.setMovementMethod(LinkMovementMethod.getInstance()); // Prevent focus...not precisely sure why, but sample code does this tv.setFocusable(false); // Set the colout to prevent flicker on click tv.setTextColor(this.getResources().getColor(android.R.color.primary_text_dark_nodisable)); System.out.println("BEF bDesc: " + (System.currentTimeMillis() - t0)); } private void clearOldInternalSpans(TextView tv) { CharSequence cs = tv.getText(); if (cs instanceof Spannable) { final Spannable s = (Spannable)cs; InternalSpan[] spans = s.getSpans(0, tv.getText().length(), InternalSpan.class); for (int i = 0; i < spans.length; i++) { s.removeSpan(spans[i]); } } } @Override public void onSaveInstanceState(Bundle outState) { Tracker.enterOnSaveInstanceState(this); super.onSaveInstanceState(outState); Tracker.exitOnSaveInstanceState(this); } @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { Tracker.enterOnActivityResult(this, requestCode, resultCode); try { super.onActivityResult(requestCode, resultCode, intent); switch(requestCode) { case ACTIVITY_EDIT_AUTHORS: if (resultCode == Activity.RESULT_OK && intent.hasExtra(CatalogueDBAdapter.KEY_AUTHOR_ARRAY)){ mEditManager.getBookData().setAuthorList(Utils.getAuthorsFromBundle(intent.getExtras())); mEditManager.setDirty(true); } else { // Even though the dialog was terminated, some authors MAY have been updated/added. mEditManager.getBookData().refreshAuthorList(mDbHelper); } // We do the fixup here because the user may have edited or merged authors; this will // have already been applied to the database so no update is necessary, but we do need // to update the data we display. boolean oldDirty = mEditManager.isDirty(); populateAuthorListField(); mEditManager.setDirty(oldDirty); case ACTIVITY_EDIT_SERIES: if (resultCode == Activity.RESULT_OK && intent.hasExtra(CatalogueDBAdapter.KEY_SERIES_ARRAY)){ mEditManager.getBookData().setSeriesList(Utils.getSeriesFromBundle(intent.getExtras())); populateSeriesListField(); mEditManager.setDirty(true); } } } finally { Tracker.exitOnActivityResult(this, requestCode, resultCode); } } @Override protected void populateAuthorListField() { ArrayList<Author> list = mEditManager.getBookData().getAuthorList(); if (list.size() != 0 && Utils.pruneList(mDbHelper, list) ) { mEditManager.setDirty(true); mEditManager.getBookData().setAuthorList(list); } super.populateAuthorListField(); } /** * Show the context menu for the cover thumbnail */ public void showCoverContextMenu() { View v = getView().findViewById(R.id.row_img); v.showContextMenu(); } @Override protected void onLoadBookDetails(BookData book, boolean setAllDone) { if (!setAllDone) mFields.setAll(book); populateFields(); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { boolean thumbVisible = BookCatalogueApp.getAppPreferences().getBoolean(FieldVisibility.prefix + "thumbnail", true); if (thumbVisible) { MenuItem thumbOptions = menu.add(0, BookEditFragmentAbstract.THUMBNAIL_OPTIONS_ID, 0, R.string.cover_options_cc_ellipsis); thumbOptions.setIcon(android.R.drawable.ic_menu_camera); thumbOptions.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); } super.onCreateOptionsMenu(menu, inflater); } /** * The callback received when the user "sets" the date in the dialog. * * Build a full or partial date in SQL format */ @Override public void onPartialDatePickerSet(int dialogId, PartialDatePickerFragment dialog, Integer year, Integer month, Integer day) { String value = Utils.buildPartialDate(year, month, day); mFields.getField(dialogId).setValue(value); dialog.dismiss(); } /** * The callback received when the user "cancels" the date in the dialog. * * Dismiss it. */ @Override public void onPartialDatePickerCancel(int dialogId, PartialDatePickerFragment dialog) { dialog.dismiss(); } /** * The callback received when the user "sets" the text editor in the text editor dialog. * * Set the appropriate field */ @Override public void onTextFieldEditorSave(int dialogId, TextFieldEditorFragment dialog, String newText) { mFields.getField(dialogId).setValue(newText); dialog.dismiss(); } /** * The callback received when the user "cancels" the text editor dialog. * * Dismiss it. */ @Override public void onTextFieldEditorCancel(int dialogId, TextFieldEditorFragment dialog) { dialog.dismiss(); } @Override public void onBookshelfCheckChanged(int dialogId, BookshelfDialogFragment dialog, boolean checked, String shelf, String textList, String encodedList) { Field fe = mFields.getField(R.id.bookshelf); fe.setValue(textList); mEditManager.getBookData().setBookshelfList(encodedList); } }