diff options
Diffstat (limited to 'src/com/android/gallery3d/ui/DetailsWindow.java')
-rw-r--r-- | src/com/android/gallery3d/ui/DetailsWindow.java | 451 |
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; + } + } + } +} |