blob: 03e21692237abd884a20663e7a8b1a09e4a29bc2 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.gallery3d.ui;
import static com.android.gallery3d.ui.DetailsWindowConfig.FONT_SIZE;
import static com.android.gallery3d.ui.DetailsWindowConfig.LEFT_RIGHT_EXTRA_PADDING;
import static com.android.gallery3d.ui.DetailsWindowConfig.LINE_SPACING;
import static com.android.gallery3d.ui.DetailsWindowConfig.PREFERRED_WIDTH;
import static com.android.gallery3d.ui.DetailsWindowConfig.TOP_BOTTOM_EXTRA_PADDING;
import com.android.gallery3d.R;
import com.android.gallery3d.app.GalleryActivity;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.MediaDetails;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.FutureListener;
import com.android.gallery3d.util.ReverseGeocoder;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.location.Address;
import android.os.Handler;
import android.os.Message;
import android.text.format.Formatter;
import android.view.MotionEvent;
import android.view.View.MeasureSpec;
import java.util.ArrayList;
import java.util.Map.Entry;
// TODO: Add scroll bar to this window.
public class DetailsWindow extends GLView {
@SuppressWarnings("unused")
private static final String TAG = "DetailsWindow";
private static final int MSG_REFRESH_LOCATION = 1;
private static final int FONT_COLOR = Color.WHITE;
private static final int CLOSE_BUTTON_SIZE = 32;
private GalleryActivity mContext;
protected Texture mBackground;
private StringTexture mTitle;
private MyDataModel mModel;
private MediaDetails mDetails;
private DetailsSource mSource;
private int mIndex;
private int mLocationIndex;
private Future<Address> mAddressLookupJob;
private Handler mHandler;
private Icon mCloseButton;
private int mMaxDetailLength;
private CloseListener mListener;
private ScrollView mScrollView;
private DetailsPanel mDetailPanel = new DetailsPanel();
public interface DetailsSource {
public int size();
public int findIndex(int indexHint);
public MediaDetails getDetails();
}
public interface CloseListener {
public void onClose();
}
public DetailsWindow(GalleryActivity activity, DetailsSource source) {
mContext = activity;
mSource = source;
mHandler = new SynchronizedHandler(activity.getGLRoot()) {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_REFRESH_LOCATION:
mModel.updateLocation((Address) msg.obj);
invalidate();
break;
}
}
};
Context context = activity.getAndroidContext();
ResourceTexture icon = new ResourceTexture(context, R.drawable.ic_menu_cancel_holo_light);
setBackground(new NinePatchTexture(context, R.drawable.popup_full_dark));
mCloseButton = new Icon(context, icon, CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE) {
@Override
protected boolean onTouch(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_UP:
if (mListener != null) mListener.onClose();
}
return true;
}
};
mScrollView = new ScrollView(context);
mScrollView.addComponent(mDetailPanel);
super.addComponent(mScrollView);
super.addComponent(mCloseButton);
reloadDetails(0);
}
public void setCloseListener(CloseListener listener) {
mListener = listener;
}
public void setBackground(Texture background) {
if (background == mBackground) return;
mBackground = background;
if (background != null && background instanceof NinePatchTexture) {
Rect p = ((NinePatchTexture) mBackground).getPaddings();
p.left += LEFT_RIGHT_EXTRA_PADDING;
p.right += LEFT_RIGHT_EXTRA_PADDING;
p.top += TOP_BOTTOM_EXTRA_PADDING;
p.bottom += TOP_BOTTOM_EXTRA_PADDING;
setPaddings(p);
} else {
setPaddings(0, 0, 0, 0);
}
Rect p = getPaddings();
mMaxDetailLength = PREFERRED_WIDTH - p.left - p.right;
invalidate();
}
public void setTitle(String title) {
mTitle = StringTexture.newInstance(title, FONT_SIZE, FONT_COLOR);
}
@Override
protected void renderBackground(GLCanvas canvas) {
if (mBackground == null) return;
int width = getWidth();
int height = getHeight();
//TODO: change alpha in the background image.
canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
canvas.setAlpha(0.7f);
mBackground.draw(canvas, 0, 0, width, height);
canvas.restore();
Rect p = getPaddings();
if (mTitle != null) mTitle.draw(canvas, p.left, p.top);
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
int height = MeasureSpec.getSize(heightSpec);
MeasureHelper.getInstance(this)
.setPreferredContentSize(PREFERRED_WIDTH, height)
.measure(widthSpec, heightSpec);
}
@Override
protected void onLayout(boolean sizeChange, int l, int t, int r, int b) {
mCloseButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int bWidth = mCloseButton.getMeasuredWidth();
int bHeight = mCloseButton.getMeasuredHeight();
int width = getWidth();
int height = getHeight();
Rect p = getPaddings();
mCloseButton.layout(width - p.right - bWidth, p.top,
width - p.right, p.top + bHeight);
mScrollView.layout(p.left, p.top + bHeight, width - p.right,
height - p.bottom);
}
public void show() {
setVisibility(GLView.VISIBLE);
requestLayout();
}
public void hide() {
setVisibility(GLView.INVISIBLE);
requestLayout();
}
public void pause() {
Future<Address> lookupJob = mAddressLookupJob;
if (lookupJob != null) {
lookupJob.cancel();
lookupJob.waitDone();
}
}
public void reloadDetails(int indexHint) {
int index = mSource.findIndex(indexHint);
if (index == -1) return;
MediaDetails details = mSource.getDetails();
if (details != null) {
if (mIndex == index && mDetails == details) return;
mIndex = index;
mDetails = details;
setDetails(details);
}
mDetailPanel.requestLayout();
}
private void setDetails(MediaDetails details) {
mModel = new MyDataModel(details);
invalidate();
}
private class AddressLookupJob implements Job<Address> {
double[] mLatlng;
protected AddressLookupJob(double[] latlng) {
mLatlng = latlng;
}
public Address run(JobContext jc) {
ReverseGeocoder geocoder = new ReverseGeocoder(mContext.getAndroidContext());
return geocoder.lookupAddress(mLatlng[0], mLatlng[1], true);
}
}
private class MyDataModel {
ArrayList<Texture> mItems;
public MyDataModel(MediaDetails details) {
Context context = mContext.getAndroidContext();
mLocationIndex = -1;
mItems = new ArrayList<Texture>(details.size());
setTitle(String.format(context.getString(R.string.sequence_in_set),
mIndex + 1, mSource.size()));
setDetails(context, details);
}
private void setDetails(Context context, MediaDetails details) {
for (Entry<Integer, Object> detail : details) {
String value;
switch (detail.getKey()) {
case MediaDetails.INDEX_LOCATION: {
value = getLocationText((double[]) detail.getValue());
break;
}
case MediaDetails.INDEX_SIZE: {
value = Formatter.formatFileSize(
context, (Long) detail.getValue());
break;
}
case MediaDetails.INDEX_WHITE_BALANCE: {
value = "1".equals(detail.getValue())
? context.getString(R.string.manual)
: context.getString(R.string.auto);
break;
}
case MediaDetails.INDEX_FLASH: {
MediaDetails.FlashState flash =
(MediaDetails.FlashState) detail.getValue();
// TODO: camera doesn't fill in the complete values, show more information
// when it is fixed.
if (flash.isFlashFired()) {
value = context.getString(R.string.flash_on);
} else {
value = context.getString(R.string.flash_off);
}
break;
}
case MediaDetails.INDEX_EXPOSURE_TIME: {
value = (String) detail.getValue();
double time = Double.valueOf(value);
if (time < 1.0f) {
value = String.format("1/%d", (int) (0.5f + 1 / time));
} else {
int integer = (int) time;
time -= integer;
value = String.valueOf(integer) + "''";
if (time > 0.0001) {
value += String.format(" 1/%d", (int) (0.5f + 1 / time));
}
}
break;
}
default: {
Object valueObj = detail.getValue();
// This shouldn't happen, log its key to help us diagnose the problem.
Utils.assertTrue(valueObj != null, "%s's value is Null",
getName(context, detail.getKey()));
value = valueObj.toString();
}
}
int key = detail.getKey();
if (details.hasUnit(key)) {
value = String.format("%s : %s %s", getName(context, key), value,
context.getString(details.getUnit(key)));
} else {
value = String.format("%s : %s", getName(context, key), value);
}
Texture label = MultiLineTexture.newInstance(
value, mMaxDetailLength, FONT_SIZE, FONT_COLOR);
mItems.add(label);
}
}
private String getLocationText(double[] latlng) {
String text = String.format("(%f, %f)", latlng[0], latlng[1]);
mAddressLookupJob = mContext.getThreadPool().submit(
new AddressLookupJob(latlng),
new FutureListener<Address>() {
public void onFutureDone(Future<Address> future) {
mAddressLookupJob = null;
if (!future.isCancelled()) {
mHandler.sendMessage(mHandler.obtainMessage(
MSG_REFRESH_LOCATION, future.get()));
}
}
});
mLocationIndex = mItems.size();
return text;
}
public void updateLocation(Address address) {
int index = mLocationIndex;
if (address != null && index >=0 && index < mItems.size()) {
Context context = mContext.getAndroidContext();
String parts[] = {
address.getAdminArea(),
address.getSubAdminArea(),
address.getLocality(),
address.getSubLocality(),
address.getThoroughfare(),
address.getSubThoroughfare(),
address.getPremises(),
address.getPostalCode(),
address.getCountryName()
};
String addressText = "";
for (int i = 0; i < parts.length; i++) {
if (parts[i] == null || parts[i].isEmpty()) continue;
if (!addressText.isEmpty()) {
addressText += ", ";
}
addressText += parts[i];
}
String text = String.format("%s : %s", getName(context,
MediaDetails.INDEX_LOCATION), addressText);
mItems.set(index, MultiLineTexture.newInstance(
text, mMaxDetailLength, FONT_SIZE, FONT_COLOR));
}
}
public Texture getView(int index) {
return mItems.get(index);
}
public int size() {
return mItems.size();
}
}
private static String getName(Context context, int key) {
switch (key) {
case MediaDetails.INDEX_TITLE:
return context.getString(R.string.title);
case MediaDetails.INDEX_DESCRIPTION:
return context.getString(R.string.description);
case MediaDetails.INDEX_DATETIME:
return context.getString(R.string.time);
case MediaDetails.INDEX_LOCATION:
return context.getString(R.string.location);
case MediaDetails.INDEX_PATH:
return context.getString(R.string.path);
case MediaDetails.INDEX_WIDTH:
return context.getString(R.string.width);
case MediaDetails.INDEX_HEIGHT:
return context.getString(R.string.height);
case MediaDetails.INDEX_ORIENTATION:
return context.getString(R.string.orientation);
case MediaDetails.INDEX_DURATION:
return context.getString(R.string.duration);
case MediaDetails.INDEX_MIMETYPE:
return context.getString(R.string.mimetype);
case MediaDetails.INDEX_SIZE:
return context.getString(R.string.file_size);
case MediaDetails.INDEX_MAKE:
return context.getString(R.string.maker);
case MediaDetails.INDEX_MODEL:
return context.getString(R.string.model);
case MediaDetails.INDEX_FLASH:
return context.getString(R.string.flash);
case MediaDetails.INDEX_APERTURE:
return context.getString(R.string.aperture);
case MediaDetails.INDEX_FOCAL_LENGTH:
return context.getString(R.string.focal_length);
case MediaDetails.INDEX_WHITE_BALANCE:
return context.getString(R.string.white_balance);
case MediaDetails.INDEX_EXPOSURE_TIME:
return context.getString(R.string.exposure_time);
case MediaDetails.INDEX_ISO:
return context.getString(R.string.iso);
default:
return "Unknown key" + key;
}
}
private class DetailsPanel extends GLView {
@Override
public void onMeasure(int widthSpec, int heightSpec) {
if (mTitle == null || mModel == null) {
MeasureHelper.getInstance(this)
.setPreferredContentSize(PREFERRED_WIDTH, 0)
.measure(widthSpec, heightSpec);
return;
}
int h = getPaddings().top + LINE_SPACING;
for (int i = 0, n = mModel.size(); i < n; ++i) {
h += mModel.getView(i).getHeight() + LINE_SPACING;
}
MeasureHelper.getInstance(this)
.setPreferredContentSize(PREFERRED_WIDTH, h)
.measure(widthSpec, heightSpec);
}
@Override
protected void render(GLCanvas canvas) {
super.render(canvas);
if (mTitle == null || mModel == null) {
return;
}
Rect p = getPaddings();
int x = p.left, y = p.top + LINE_SPACING;
for (int i = 0, n = mModel.size(); i < n ; i++) {
Texture t = mModel.getView(i);
t.draw(canvas, x, y);
y += t.getHeight() + LINE_SPACING;
}
}
}
}