package com.eleybourn.bookcatalogue;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.Toast;
import com.eleybourn.bookcatalogue.CoverBrowser.OnImageSelectedListener;
import com.eleybourn.bookcatalogue.Fields.Field;
import com.eleybourn.bookcatalogue.booklist.DatabaseDefinitions;
import com.eleybourn.bookcatalogue.cropper.CropCropImage;
import com.eleybourn.bookcatalogue.debug.Tracker;
import com.eleybourn.bookcatalogue.utils.HintManager;
import com.eleybourn.bookcatalogue.utils.Logger;
import com.eleybourn.bookcatalogue.utils.StorageUtils;
import com.eleybourn.bookcatalogue.utils.Utils;
/**
* Abstract class for creating activities containing book details.
* Here we define common method for all childs: database and background initializing,
* initializing fields and display metrics and other common tasks.
* @author n.silin
*/
public abstract class BookDetailsAbstract extends BookEditFragmentAbstract {
public static final Character BOOKSHELF_SEPERATOR = ',';
// Target size of a thumbnail in edit dialog and zoom dialog (bbox dim)
protected static final int MAX_EDIT_THUMBNAIL_SIZE = 256;
protected static final int MAX_ZOOM_THUMBNAIL_SIZE=1024;
private static final int CONTEXT_ID_DELETE = 1;
private static final int CONTEXT_SUBMENU_REPLACE_THUMB = 2;
private static final int CONTEXT_ID_SUBMENU_ROTATE_THUMB = 3;
private static final int CONTEXT_ID_CROP_THUMB = 6;
private static final int CODE_ADD_PHOTO = 21;
private static final int CODE_ADD_GALLERY = 22;
private static final int CONTEXT_ID_SHOW_ALT_COVERS = 23;
private static final int CONTEXT_ID_ROTATE_THUMB_CW = 31;
private static final int CONTEXT_ID_ROTATE_THUMB_CCW = 32;
private static final int CONTEXT_ID_ROTATE_THUMB_180 = 33;
private static final int CODE_CROP_RESULT_EXTERNAL = 42;
private static final int CODE_CROP_RESULT_INTERNAL = 43;
private CoverBrowser mCoverBrowser = null;
/** Counter used to prevent images being reused accidentally */
private static int mTempImageCounter = 0;
/** Used to display a hint if user rotates a camera image */
private boolean mGotCameraImage = false;
protected android.util.DisplayMetrics mMetrics;
/** Minimum of MAX_EDIT_THUMBNAIL_SIZE and 1/3rd of largest screen dimension */
protected Integer mThumbEditSize;
/** Zoom size is minimum of MAX_ZOOM_THUMBNAIL_SIZE and largest screen dimension. */
protected Integer mThumbZoomSize;
/**
* Handler to process a cover selected from the CoverBrowser.
*/
private OnImageSelectedListener mOnImageSelectedListener = new OnImageSelectedListener() {
@Override
public void onImageSelected(String fileSpec) {
if (mCoverBrowser != null && fileSpec != null) {
// Get the current file
File bookFile = getCoverFile(mEditManager.getBookData().getRowId());
// Get the new file
File newFile = new File(fileSpec);
// Overwrite with new file
newFile.renameTo(bookFile);
// update current activity
setCoverImage();
}
if (mCoverBrowser != null)
mCoverBrowser.dismiss();
mCoverBrowser = null;
}};
/**
* Listener for creating context menu for book thumbnail.
*/
private OnCreateContextMenuListener mCreateBookThumbContextMenuListener = new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
MenuItem delete = menu.add(0, CONTEXT_ID_DELETE, 0, R.string.menu_delete_thumb);
delete.setIcon(android.R.drawable.ic_menu_delete);
// Creating submenu item for rotate
android.view.SubMenu replaceSubmenu = menu.addSubMenu(0, CONTEXT_SUBMENU_REPLACE_THUMB, 2,
R.string.menu_replace_thumb);
replaceSubmenu.setIcon(android.R.drawable.ic_menu_gallery);
MenuItem add_photo = replaceSubmenu.add(0, CODE_ADD_PHOTO, 1, R.string.menu_add_thumb_photo);
add_photo.setIcon(android.R.drawable.ic_menu_camera);
MenuItem add_gallery = replaceSubmenu.add(0, CODE_ADD_GALLERY, 2, R.string.menu_add_thumb_gallery);
add_gallery.setIcon(android.R.drawable.ic_menu_gallery);
MenuItem alt_covers = replaceSubmenu.add(0, CONTEXT_ID_SHOW_ALT_COVERS, 3, R.string.menu_thumb_alt_editions);
alt_covers.setIcon(android.R.drawable.ic_menu_zoom);
// Implementing submenu for rotate
android.view.SubMenu submenu = menu.addSubMenu(0, CONTEXT_ID_SUBMENU_ROTATE_THUMB, 3, R.string.menu_rotate_thumb);
add_gallery.setIcon(android.R.drawable.ic_menu_rotate);
MenuItem rotate_photo_cw = submenu.add(0, CONTEXT_ID_ROTATE_THUMB_CW, 1, R.string.menu_rotate_thumb_cw);
rotate_photo_cw.setIcon(android.R.drawable.ic_menu_rotate);
MenuItem rotate_photo_ccw = submenu.add(0, CONTEXT_ID_ROTATE_THUMB_CCW, 2, R.string.menu_rotate_thumb_ccw);
rotate_photo_ccw.setIcon(android.R.drawable.ic_menu_rotate);
MenuItem rotate_photo_180 = submenu.add(0, CONTEXT_ID_ROTATE_THUMB_180, 3, R.string.menu_rotate_thumb_180);
rotate_photo_180.setIcon(android.R.drawable.ic_menu_rotate);
MenuItem crop_thumb = menu.add(0, CONTEXT_ID_CROP_THUMB, 4, R.string.menu_crop_thumb);
crop_thumb.setIcon(android.R.drawable.ic_menu_crop);
}
};
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Tracker.handleEvent(this, "onActivityResult(" + requestCode + "," + resultCode + ")", Tracker.States.Enter);
try {
super.onActivityResult(requestCode, resultCode, intent);
switch(requestCode) {
case CODE_ADD_PHOTO:
if (resultCode == Activity.RESULT_OK && intent != null && intent.getExtras() != null){
File cameraFile = getCameraImageFile();
Bitmap x = (Bitmap) intent.getExtras().get("data");
if (x != null && x.getWidth() > 0 && x.getHeight() > 0) {
Matrix m = new Matrix();
m.postRotate(BookCatalogueApp.getAppPreferences().getInt(BookCataloguePreferences.PREF_AUTOROTATE_CAMERA_IMAGES, 90));
x = Bitmap.createBitmap(x, 0, 0, x.getWidth(), x.getHeight(), m, true);
// Create a file to copy the thumbnail into
FileOutputStream f = null;
try {
f = new FileOutputStream(cameraFile.getAbsoluteFile());
} catch (FileNotFoundException e) {
Logger.logError(e);
return;
}
x.compress(Bitmap.CompressFormat.PNG, 100, f);
cropCoverImage(cameraFile);
mGotCameraImage = true;
} else {
Tracker.handleEvent(this, "onActivityResult(" + requestCode + "," + resultCode + ") - camera image empty", Tracker.States.Running);
}
}
return;
case CODE_ADD_GALLERY:
if (resultCode == Activity.RESULT_OK){
Uri selectedImageUri = intent.getData();
if (selectedImageUri != null) {
// Use the original BC code for anything that has a 'content' scheme and if pre-KitKat
// TODO: When KitKat is at 80% penetration(!!!), remove this side of the 'if' statement.
if (Build.VERSION.SDK_INT < 19 && selectedImageUri.getScheme().equalsIgnoreCase("content")) {
String[] projection = { MediaStore.Images.Media.DATA };
Cursor cursor = this.getActivity().getContentResolver().query(selectedImageUri, projection, null, null, null);
try {
int column_index = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
if (column_index < 0 || !cursor.moveToFirst()) {
Logger.logError(new RuntimeException("Add from gallery failed (col = " + column_index +"), name = " + MediaStore.Images.Media.DATA));
// This should not happen, but tell the user and log something
String s = getResources().getString(R.string.no_image_found) + ". " + getResources().getString(R.string.if_the_problem_persists);
Toast.makeText(getActivity(), s, Toast.LENGTH_LONG).show();
} else {
String selectedImagePath = cursor.getString(column_index);
File thumb = new File(selectedImagePath);
File real = getCoverFile(mEditManager.getBookData().getRowId());
try {
copyFile(thumb, real);
} catch (IOException e) {
Logger.logError(e, "copyImage failed in add from gallery");
String s = getResources().getString(R.string.could_not_copy_image) + ". " + getResources().getString(R.string.if_the_problem_persists);
Toast.makeText(getActivity(), s, Toast.LENGTH_LONG).show();
}
// Update the ImageView with the new image
setCoverImage();
}
} finally {
try {
if (cursor != null && !cursor.isClosed())
cursor.close();
} catch (Exception e) {
}
}
} else {
boolean imageOk = false;
// If no 'content' scheme, then use the content resolver.
try {
InputStream in = getActivity().getContentResolver().openInputStream(selectedImageUri);
imageOk = Utils.saveInputToFile(in, getCoverFile(mEditManager.getBookData().getRowId()));
} catch (FileNotFoundException e) {
Logger.logError(e, "Unable to copy content to file");
}
if (imageOk) {
// Update the ImageView with the new image
setCoverImage();
} else {
String s = getResources().getString(R.string.could_not_copy_image) + ". " + getResources().getString(R.string.if_the_problem_persists);
Toast.makeText(getActivity(), s, Toast.LENGTH_LONG).show();
}
}
} else {
/* Deal with the case where the chooser returns a null intent. This seems to happen
* when the filename is not properly understood by the choose (eg. an apostrophe in
* the file name confuses ES File Explorer in the current version as of 23-Sep-2012. */
Toast.makeText(getActivity(), R.string.could_not_copy_image, Toast.LENGTH_LONG).show();
}
}
return;
case CODE_CROP_RESULT_EXTERNAL:
{
File thumbFile = getCoverFile(mEditManager.getBookData().getRowId());
File cropped = this.getCroppedImageFileName();
if (resultCode == Activity.RESULT_OK){
if (cropped.exists()) {
// Update the ImageView with the new image
cropped.renameTo(thumbFile);
setCoverImage();
} else {
Tracker.handleEvent(this, "onActivityResult(" + requestCode + "," + resultCode + ") - result OK, no image file", Tracker.States.Running);
}
} else {
Tracker.handleEvent(this, "onActivityResult(" + requestCode + "," + resultCode + ") - bad result", Tracker.States.Running);
if (cropped.exists())
cropped.delete();
}
return;
}
case CODE_CROP_RESULT_INTERNAL:
{
File thumbFile = getCoverFile(mEditManager.getBookData().getRowId());
File cropped = this.getCroppedImageFileName();
if (resultCode == Activity.RESULT_OK) {
if (cropped.exists()) {
cropped.renameTo(thumbFile);
// Update the ImageView with the new image
setCoverImage();
}
}
return;
}
}
} finally {
Tracker.handleEvent(this, "onActivityResult", Tracker.States.Exit);
}
}
/* Note that you should use setContentView() method in descendant before
* running this.
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// See how big the display is and use that to set bitmap sizes
setDisplayMetrics();
initThumbSizes();
initFields();
//Set zooming by default on clicking on image
getView().findViewById(R.id.row_img).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showZoomedThumb(mEditManager.getBookData().getRowId());
}
});
//Utils.initBackground(R.drawable.bc_background_gradient_dim, this, false);
}
@Override
public void onPause() {
Tracker.enterOnPause(this);
super.onPause();
// Close down the cover browser.
if (mCoverBrowser != null) {
mCoverBrowser.dismiss();
mCoverBrowser = null;
}
Tracker.exitOnPause(this);
}
@Override
public void onResume() {
Tracker.enterOnResume(this);
super.onResume();
// Fix background
//Utils.initBackground(R.drawable.bc_background_gradient_dim, this, false);
Tracker.exitOnResume(this);
}
@Override
public void onDestroy() {
Tracker.enterOnDestroy(this);
super.onDestroy();
mDbHelper.close();
Tracker.exitOnDestroy(this);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
Tracker.handleEvent(this, "Context Menu Item " + item.getItemId(), Tracker.States.Enter);
try {
ImageView iv = (ImageView)getView().findViewById(R.id.row_img);
File thumbFile = getCoverFile(mEditManager.getBookData().getRowId());
switch(item.getItemId()) {
case CONTEXT_ID_DELETE:
deleteThumbnail();
Utils.fetchFileIntoImageView(thumbFile, iv, mThumbEditSize, mThumbEditSize, true);
return true;
case CONTEXT_ID_SUBMENU_ROTATE_THUMB:
// Just a submenu; skip, but display a hint if user is rotating a camera image
if (mGotCameraImage) {
HintManager.displayHint(getActivity(), R.string.hint_autorotate_camera_images, null);
mGotCameraImage = false;
}
return true;
case CONTEXT_ID_ROTATE_THUMB_CW:
rotateThumbnail(90);
Utils.fetchFileIntoImageView(thumbFile, iv, mThumbEditSize, mThumbEditSize, true);
return true;
case CONTEXT_ID_ROTATE_THUMB_CCW:
rotateThumbnail(-90);
Utils.fetchFileIntoImageView(thumbFile, iv, mThumbEditSize, mThumbEditSize, true);
return true;
case CONTEXT_ID_ROTATE_THUMB_180:
rotateThumbnail(180);
Utils.fetchFileIntoImageView(thumbFile, iv, mThumbEditSize, mThumbEditSize, true);
return true;
case CODE_ADD_PHOTO:
// Increment the temp counter and cleanup the temp directory
mTempImageCounter++;
cleanupTempImages();
Intent pintent = null;
// Get a photo
pintent = new Intent("android.media.action.IMAGE_CAPTURE");
// We don't do this because we have no reliable way to rotate a large image
// without producing memory exhaustion; Android does not include a file-based
// image rotation.
// File f = this.getCameraImageFile();
// if (f.exists())
// f.delete();
// pintent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
startActivityForResult(pintent, CODE_ADD_PHOTO);
return true;
case CODE_ADD_GALLERY:
Intent gintent = new Intent();
gintent.setType("image/*");
gintent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(gintent, getResources().getString(R.string.select_picture)), CODE_ADD_GALLERY);
return true;
case CONTEXT_ID_CROP_THUMB:
cropCoverImage(thumbFile);
return true;
case CONTEXT_ID_SHOW_ALT_COVERS:
String isbn = mFields.getField(R.id.isbn).getValue().toString();
if (isbn == null || isbn.trim().length() == 0) {
Toast.makeText(getActivity(), getResources().getString(R.string.editions_require_isbn), Toast.LENGTH_LONG).show();
} else {
mCoverBrowser = new CoverBrowser(getActivity(), mMetrics, isbn, mOnImageSelectedListener);
mCoverBrowser.showEditionCovers();
}
return true;
}
return super.onContextItemSelected(item);
} finally {
Tracker.handleEvent(this, "Context Menu Item " + item.getItemId(), Tracker.States.Exit);
}
}
/**
* Delete everything in the temp file directory
*/
private void cleanupTempImages() {
File[] files = getTempImageDir().listFiles();
if (files != null) {
for (File f: files) {
try {
f.delete();
} catch (Exception e) {
Logger.logError(e, "Unable to clean up temp file");
}
}
}
}
private void copyFile(File src, File dst) throws IOException {
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dst);
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
try {
inChannel.transferTo(0, inChannel.size(), outChannel);
} finally {
if (inChannel != null){
inChannel.close();
}
if (outChannel != null){
outChannel.close();
}
if(fis != null){
fis.close();
}
if(fos != null){
fos.close();
}
}
}
private void cropCoverImage(File thumbFile) {
String prefName = BookCataloguePreferences.PREF_USE_EXTERNAL_IMAGE_CROPPER;
boolean useExt = BookCatalogueApp.getAppPreferences().getBoolean(prefName, false);
if (useExt) {
cropCoverImageExternal(thumbFile);
} else {
cropCoverImageInternal(thumbFile);
}
}
private void cropCoverImageInternal(File thumbFile) {
Intent crop_intent = new Intent(getActivity(), CropCropImage.class);
// here you have to pass absolute path to your file
crop_intent.putExtra("image-path", thumbFile.getAbsolutePath());
crop_intent.putExtra("scale", true);
String prefName = BookCataloguePreferences.PREF_CROP_FRAME_WHOLE_IMAGE;
crop_intent.putExtra("whole-image", BookCatalogueApp.getAppPreferences().getBoolean(prefName, false));
// Get and set the output file spec, and make sure it does not already exist.
File cropped = this.getCroppedImageFileName();
if (cropped.exists()) {
cropped.delete();
}
crop_intent.putExtra("output", cropped.getAbsolutePath());
startActivityForResult(crop_intent, CODE_CROP_RESULT_INTERNAL);
}
private void cropCoverImageExternal(File thumbFile) {
Tracker.handleEvent(this, "cropCoverImageExternal", Tracker.States.Enter);
try {
Intent intent = new Intent("com.android.camera.action.CROP");
// this will open any image file
intent.setDataAndType(Uri.fromFile(new File(thumbFile.getAbsolutePath())), "image/*");
intent.putExtra("crop", "true");
// This defines the aspect ratio
// intent.putExtra("aspectX", 1);
// intent.putExtra("aspectY", 1);
// This defines the output bitmap size
// intent.putExtra("outputX", 3264);
// intent.putExtra("outputY", 2448);
// intent.putExtra("outputX", mThumbZoomSize*2);
// intent.putExtra("outputY", mThumbZoomSize*2);
intent.putExtra("scale", true);
intent.putExtra("noFaceDetection", true);
// True to return a Bitmap, false to directly save the cropped iamge
intent.putExtra("return-data", false);
// Save output image in uri
File cropped = this.getCroppedImageFileName();
if (cropped.exists())
cropped.delete();
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(cropped.getAbsolutePath())));
List<ResolveInfo> list = getActivity().getPackageManager().queryIntentActivities( intent, 0 );
int size = list.size();
if (size == 0) {
Toast.makeText(getActivity(), "Can not find image crop app", Toast.LENGTH_SHORT).show();
} else {
startActivityForResult(intent, CODE_CROP_RESULT_EXTERNAL);
}
} finally {
Tracker.handleEvent(this, "cropCoverImageExternal", Tracker.States.Exit);
}
}
/**
* Delete the provided thumbnail from the sdcard
* @param id The id of the book (and thumbnail) to delete
*/
private void deleteThumbnail() {
try {
File thumbFile = getCoverFile(mEditManager.getBookData().getRowId());
if (thumbFile != null && thumbFile.exists())
thumbFile.delete();
} catch (Exception e) {
Logger.logError(e);
}
// Make sure the cached thumbnails (if present) are deleted
invalidateCachedThumbnail();
}
/**
* Get a temp file for camera images
*/
private File getCameraImageFile() {
return new File(getTempImageDir().getAbsolutePath() + "/camera" + mTempImageCounter + ".jpg");
}
/**
* Get the File object for the cover of the book we are editing. If the boo
* is new, return the standard temp file.
*/
protected File getCoverFile(Long rowId) {
if (rowId == null || rowId == 0)
return CatalogueDBAdapter.getTempThumbnail();
else
return CatalogueDBAdapter.fetchThumbnailByUuid(mDbHelper.getBookUuid(rowId));
}
/**
* Get a temp file for cropping output
*/
private File getCroppedImageFileName() {
return new File(getTempImageDir().getAbsolutePath() + "/cropped" + mTempImageCounter + ".jpg");
}
/**
* Get a temp directory for image manipulation (create if necessary)
*/
private File getTempImageDir() {
File f = new File(StorageUtils.getSharedStoragePath() + "/tmp_images/");
if (!f.exists())
f.mkdirs();
return f;
}
/**
* Populate Author field by data from {@link #mAuthorList}.
* If there is no data shows "Set author" text defined in resources.
* <p>
* Be sure that you get {@link #mAuthorList}. See {@link #populateFieldsFromDb(Long)}
* for example.
*/
protected void populateAuthorListField() {
String newText = mEditManager.getBookData().getAuthorTextShort();
if (newText == null || newText.equals("")) {
newText = getResources().getString(R.string.set_authors);
}
mFields.getField(R.id.author).setValue(newText);
}
/**
* Populate Series field by data from {@link #mSeriesList}.
* If there is no data shows "Set series..." text defined in resources.
* <p>
* Be sure that you get {@link #mSeriesList}. See {@link #populateFieldsFromDb(Long)}
* for example.
*/
protected void populateSeriesListField() {
String newText;
int size = 0;
ArrayList<Series> list = mEditManager.getBookData().getSeriesList();
try {
size = list.size();
} catch (NullPointerException e) {
size = 0;
}
if (size == 0) {
newText = getResources().getString(R.string.set_series);
} else {
boolean trimmed = Utils.pruneSeriesList(list);
trimmed |= Utils.pruneList(mDbHelper, list);
if (trimmed) {
mEditManager.getBookData().setSeriesList(list);
}
newText = list.get(0).getDisplayName();
if (list.size() > 1)
newText += " " + getResources().getString(R.string.and_others);
}
mFields.getField(R.id.series).setValue(newText);
}
/**
* Rotate the thumbnail a specified amount
* @param id
*/
private void rotateThumbnail(long angle) {
boolean retry = true;
while (retry) {
try {
File thumbFile = getCoverFile(mEditManager.getBookData().getRowId());
Bitmap origBm = Utils.fetchFileIntoImageView(thumbFile, null, mThumbZoomSize*2, mThumbZoomSize*2, true );
if (origBm == null)
return;
Matrix m = new Matrix();
m.postRotate(angle);
Bitmap rotBm = Bitmap.createBitmap(origBm, 0, 0, origBm.getWidth(), origBm.getHeight(), m, true);
if (rotBm != origBm) {
origBm.recycle();
}
/* Create a file to copy the thumbnail into */
FileOutputStream f = null;
try {
f = new FileOutputStream(thumbFile.getAbsoluteFile());
} catch (FileNotFoundException e) {
Logger.logError(e);
return;
}
rotBm.compress(Bitmap.CompressFormat.PNG, 100, f);
rotBm.recycle();
} catch (java.lang.OutOfMemoryError e) {
if (retry) {
System.gc();
} else {
throw new RuntimeException(e);
}
}
retry = false;
}
}
/**
* Ensure that the cached thumbnails for this book are deleted (if present)
*/
private void invalidateCachedThumbnail() {
final Long rowId = mEditManager.getBookData().getRowId();
if (rowId != null && rowId != 0) {
try {
String hash = mDbHelper.getBookUuid(rowId);
Utils u = new Utils();
u.deleteCachedBookCovers(hash);
} catch (Exception e) {
Logger.logError(e,"Error cleaning up cached cover images");
}
}
}
/**
* Add all book fields with corresponding validators.
*/
protected void initFields() {
final View root = getView();
/* Title has some post-processing on the text, to move leading 'A', 'The' etc to the end.
* While we could do it in a formatter, it it not really a display-oriented function and
* is handled in preprocessing in the database layer since it also needs to be applied
* to imported record etc. */
mFields.add(R.id.title, CatalogueDBAdapter.KEY_TITLE, null);
/* Anthology needs special handling, and we use a formatter to do this. If the original
* value was 0 or 1, then setting/clearing it here should just set the new value to 0 or 1.
* However...if if the original value was 2, then we want setting/clearing to alternate
* between 2 and 0, not 1 and 0.
* So, despite if being a checkbox, we use an integerValidator and use a special formatter.
* We also store it in the tag field so that it is automatically serialized with the
* activity. */
mFields.add(R.id.anthology, BookData.KEY_ANTHOLOGY, null);
mFields.add(R.id.author, "", CatalogueDBAdapter.KEY_AUTHOR_FORMATTED, null);
mFields.add(R.id.isbn, CatalogueDBAdapter.KEY_ISBN, null);
if (root.findViewById(R.id.publisher) != null)
mFields.add(R.id.publisher, CatalogueDBAdapter.KEY_PUBLISHER, null);
if (root.findViewById(R.id.date_published) != null)
mFields.add(R.id.date_published, CatalogueDBAdapter.KEY_DATE_PUBLISHED, CatalogueDBAdapter.KEY_DATE_PUBLISHED,
null, new Fields.DateFieldFormatter());
mFields.add(R.id.series, CatalogueDBAdapter.KEY_SERIES_NAME, CatalogueDBAdapter.KEY_SERIES_NAME, null);
mFields.add(R.id.list_price, "list_price", null);
mFields.add(R.id.pages, CatalogueDBAdapter.KEY_PAGES, null);
mFields.add(R.id.format, CatalogueDBAdapter.KEY_FORMAT, null);
//mFields.add(R.id.bookshelf, CatalogueDBAdapter.KEY_BOOKSHELF, null);
mFields.add(R.id.description, CatalogueDBAdapter.KEY_DESCRIPTION, null)
.setShowHtml(true);
mFields.add(R.id.genre, CatalogueDBAdapter.KEY_GENRE, null);
mFields.add(R.id.language, DatabaseDefinitions.DOM_LANGUAGE.name, null);
mFields.add(R.id.row_img, "", "thumbnail", null);
mFields.getField(R.id.row_img).getView().setOnCreateContextMenuListener(mCreateBookThumbContextMenuListener);
mFields.add(R.id.format_button, "", CatalogueDBAdapter.KEY_FORMAT, null);
mFields.add(R.id.bookshelf, "bookshelf_text", null).doNoFetch = true; // Output-only field
mFields.add(R.id.signed, CatalogueDBAdapter.KEY_SIGNED, null);
}
/**
* Initializes {@link #mThumbEditSize} and {@link #mThumbZoomSize} values according
* to screen size and {@link #MAX_EDIT_THUMBNAIL_SIZE}, {@link #MAX_ZOOM_THUMBNAIL_SIZE}
* values.<p>
* Be sure that you set {@link #mMetrics} before. See {@link #setDisplayMetrics()}
* for it.
*/
private void initThumbSizes() {
mThumbEditSize = Math.min(MAX_EDIT_THUMBNAIL_SIZE, Math.max(mMetrics.widthPixels, mMetrics.heightPixels) / 3);
mThumbZoomSize = Math.min(MAX_ZOOM_THUMBNAIL_SIZE, Math.max(mMetrics.widthPixels, mMetrics.heightPixels));
}
/**
* Get display metrics and set {@link #mMetrics} with it.
*/
private void setDisplayMetrics(){
mMetrics = new android.util.DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
}
/**
* Populate all fields (See {@link #mFields} ) except of authors and series fields with
* data from database. To set authors and series fields use {@link #populateAuthorListField()}
* and {@link #populateSeriesListField()} methods.<br>
* Also sets {@link #mAuthorList} and {@link #mSeriesList} values with data from database.
* Data defined by its _id in db.
* @param rowId database row id of the selected book.
*/
protected void populateFieldsFromBook(BookData book) {
// From the database (edit)
try {
populateBookDetailsFields(book);
setBookThumbnail(book.getRowId(), mThumbEditSize, mThumbEditSize);
} catch (Exception e) {
Logger.logError(e);
}
populateBookshelvesField(mFields, book);
}
/**
* Inflates all fields with data from cursor and populates UI fields with it.
* Also set thumbnail of the book.
* @param rowId database row _id of the book
* @param bookCursor cursor with information of the book
*/
protected void populateBookDetailsFields(BookData book){
//Set anthology field
Integer anthNo = book.getInt(BookData.KEY_ANTHOLOGY);
mFields.getField(R.id.anthology).setValue(anthNo.toString()); // Set checked if anthNo != 0
}
/**
* Sets book thumbnail
*/
protected void setBookThumbnail(Long rowId, int maxWidth, int maxHeight){
// Sets book thumbnail
ImageView iv = (ImageView) getView().findViewById(R.id.row_img);
Utils.fetchFileIntoImageView(getCoverFile(rowId), iv, maxWidth, maxHeight, true);
}
/**
* Shows zoomed thumbnail in dialog. Closed by click on image area.
* @param rowId database row id for getting correct file
*/
private void showZoomedThumb(Long rowId) {
// Create dialog and set layout
final Dialog dialog = new Dialog(getActivity());
dialog.setContentView(R.layout.zoom_thumb_dialog);
// Check if we have a file and/or it is valid
File thumbFile = getCoverFile(rowId);
if (thumbFile == null || !thumbFile.exists()) {
dialog.setTitle(getResources().getString(R.string.cover_not_set));
} else {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
BitmapFactory.decodeFile(thumbFile.getAbsolutePath(), opt);
// If no size info, assume file bad and return appropriate icon
if (opt.outHeight <= 0 || opt.outWidth <= 0) {
dialog.setTitle(getResources().getString(R.string.cover_corrupt));
} else {
dialog.setTitle(getResources().getString(R.string.cover_detail));
ImageView cover = new ImageView(getActivity());
Utils.fetchFileIntoImageView(thumbFile, cover, mThumbZoomSize, mThumbZoomSize, true);
cover.setAdjustViewBounds(true);
cover.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
dialog.addContentView(cover, lp);
}
}
dialog.show();
}
/**
* Gets all bookshelves for the book from database and populate corresponding
* filed with them.
* @param fields Fields containing book information
* @param rowId Database row _id of the book
* @return true if populated, false otherwise
*/
protected boolean populateBookshelvesField(Fields fields, BookData book){
boolean result = false;
try {
// Display the selected bookshelves
Field bookshelfTextFe = fields.getField(R.id.bookshelf);
String text = book.getBookshelfText();
bookshelfTextFe.setValue(book.getBookshelfText());
if (!text.equals("")) {
result = true; // One or more bookshelves have been set
}
} catch (Exception e) {
Logger.logError(e);
}
return result;
}
protected void setCoverImage() {
ImageView iv = (ImageView) getView().findViewById(R.id.row_img);
Utils.fetchFileIntoImageView(getCoverFile(mEditManager.getBookData().getRowId()), iv, mThumbEditSize, mThumbEditSize, true );
// Make sure the cached thumbnails (if present) are deleted
invalidateCachedThumbnail();
}
}