summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/ui/DetailsWindow.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/ui/DetailsWindow.java')
-rw-r--r--src/com/android/gallery3d/ui/DetailsWindow.java451
1 files changed, 451 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/ui/DetailsWindow.java b/src/com/android/gallery3d/ui/DetailsWindow.java
new file mode 100644
index 000000000..03e216922
--- /dev/null
+++ b/src/com/android/gallery3d/ui/DetailsWindow.java
@@ -0,0 +1,451 @@
+/*
+ * 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;
+ }
+ }
+ }
+}