/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community 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.osedu.org/licenses/ECL-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 tufts.vue.ui; import tufts.Util; import tufts.vue.*; import tufts.vue.gui.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.text.JTextComponent; import javax.swing.border.*; import java.awt.datatransfer.*; /** * Display a preview of the selected resource. E.g., and image or an icon. * * @version $Revision: 1.38 $ / $Date: 2010-02-03 19:16:31 $ / $Author: mike $ * @author Scott Fraize */ public class PreviewPane extends JPanel implements Images.Listener, Runnable, MouseWheelListener { private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(PreviewPane.class); private final Image NoImage = VueResources.getImage("/icon_noimage64.gif"); private static boolean FirstPreview = true; private Resource mResource; private Object mPreviewData; private Image mImage; private int mImageWidth; private int mImageHeight; private boolean isLoading = false; private final JLabel StatusLabel = new JLabel(VueResources.getString("jlabel.status"), JLabel.CENTER); private final int MinHeight = 128; private final int MaxHeight = 1024; PreviewPane() { super(new BorderLayout()); setOpaque(false); //StatusLabel.setLineWrap(true); //StatusLabel.setAlignmentX(0.5f); //StatusLabel.setAlignmentY(0.5f); //StatusLabel.setPreferredSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)); //StatusLabel.setBorder(new LineBorder(Color.red)); StatusLabel.setForeground(Color.darkGray); StatusLabel.setVisible(false); add(StatusLabel); setHeight(MinHeight); addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(MouseEvent me){ if (mResource != null && me.getClickCount() == 2) mResource.displayContent(); } }); addMouseMotionListener(new java.awt.event.MouseMotionAdapter() { public void mouseDragged(MouseEvent me){ if (mResource != null) { GUI.startSystemDrag(PreviewPane.this, me, mImage, new GUI.ResourceTransfer(mResource)); // start caching the non-preview versiomn of the image if it's not already there // todo: make this a low-priority disk-cache only, then if it's actually // dropped, we can create the in-memory image //Images.getImage(mResource, PreviewPane.this); // need to set preview data so that when this comes back, we don't ignore it. // Tho then we need to not clear existing thumbnail, but can put "loading" over it... } } }); } @Override public void addNotify() { super.addNotify(); GUI.MouseWheelRelay.addScrollPaneIntercept(this, this); } protected void setHeight(int h) { Dimension d = new Dimension(200,h); setMinimumSize(d); setPreferredSize(d); } public void mouseWheelMoved(MouseWheelEvent e) { if (GUI.noModifierKeysDown(e)) return; e.consume(); //int height = getPreferredSize().height; int height = getHeight(); int rotation = e.getWheelRotation(); if (rotation < 0) height += MinHeight; else if (rotation > 0) height -= MinHeight; if (height < MinHeight) height = MinHeight; else if (height > MaxHeight) height = MaxHeight; setHeight(height); } private void showLoadingStatus(Object data) { if (DEBUG.Enabled) StatusLabel.setText(""+data); else StatusLabel.setText(VueResources.getString("addLibrary.loading.label")); StatusLabel.setVisible(true); } private void status(String msg) { StatusLabel.setText("<HTML>"+msg); StatusLabel.setVisible(true); } private void clearStatus() { StatusLabel.setVisible(false); } private static final boolean LazyLoadOnPaint = false; synchronized void loadResource(Resource r) { if (DEBUG.RESOURCE || DEBUG.IMAGE) out("loadResource: " + r); // CURRENTLY: if a URLResource has already cached image, it will // return SELF to be used again, otherwise, it returns // the URL (messy...) mResource = r; if (r != null) mPreviewData = r.getPreview(); else mPreviewData = null; mImage = null; // if (mPreviewData == null && mResource.isImage()) // mPreviewData = mResource; if (DEBUG.RESOURCE) out(" got preview: " + mPreviewData); // TODO: somehow, Component.isShowing starts lying to us(!!) if // we request too many image loads in quick succession // (try arrowing down a pathway list of items that all // have thumbshots) This on Leopard Java 1.5 as of 2008-05-21. if (!LazyLoadOnPaint || isShowing()) { if (false && DEBUG.IMAGE && !isShowing()) { Log.debug("claims not visible on screen, SKIPPING ON DEBUG"); } else { loadPreview(mPreviewData); FirstPreview = false; } } else { if (FirstPreview /*&& mPreviewData != null*/) { FirstPreview = false; //Widget.setExpanded(PreviewPane.this, true); // This now handled in Widget.java: //GUI.makeVisibleOnScreen(PreviewPane.this); } else { if (DEBUG.RESOURCE || DEBUG.IMAGE) out("not showing: no action"); } } } private void loadPreview(Object previewData) { // todo: handle if preview is a Component, // todo: handle a String as preview data. if (false /*&& r.getIcon() != null*/) { // these not currently valid from Osid2AssetResource (size=-1x-1) //displayIcon(r.getIcon()); } else if (previewData == null) { displayImage(NoImage); } else if (previewData instanceof java.awt.Component) { Log.info("TODO: handle Component preview " + previewData); displayImage(NoImage); } else if (previewData != null) { // todo: check an Images.isImageableSource if (previewData instanceof Image) displayImage((Image)previewData); else loadImage(previewData); } else { displayImage(NoImage); } } // TODO: if this triggered from an LWImage selection, and LWImage had // an image error, also notify the LWImage of good data if it comes // in as a result of selection. // TODO: make the preview handling / image loading code in ResourceIcon generic // enough that PreviewPane can use a ResourceIcon instance or subclass, as the code // here is almost identical to that in ResourceIcon. private synchronized void loadImage(Object imageData) { if (DEBUG.IMAGE) out("loadImage " + Util.tags(imageData)); // test of synchronous loading: //out("***GOT IMAGE " + Images.getImage(imageData)); // TODO: refactor this class to use an ImageRef. It'll make // things simpler here, and it will remove the hard reference // to an Image we're keeping which prevents it from being // garbage-collected. if (Images.getImage(imageData, this) == null) { // will make callback to gotImage when we have it isLoading = true; showLoadingStatus(imageData); } else { // gotImage has already been called isLoading = false; } } // /** @see Images.Listener */ // public void gotImageUpdate(Object key, Images.Progress p) {} /** @see Images.Listener */ public void gotImageSize(Object imageSrc, int width, int height, long byteSize, int[] ss) { if (imageSrc != mPreviewData) return; mImageWidth = width; mImageHeight = height; } /** @see Images.Listener */ public void gotImageProgress(Object imageSrc, long bytesSoFar, float pct) {} /** @see Images.Listener */ public synchronized void gotImage(Object imageSrc, Images.Handle handle) { if (imageSrc != mPreviewData && !imageSrc.equals(mPreviewData)) { if (DEBUG.Enabled) Log.info("skipping: image source != mPreviewData;\n\t" + Util.tags(imageSrc) + "\n\t" + Util.tags(mPreviewData)); } else { displayImage(handle.image); isLoading = false; } } /** @see Images.Listener */ public synchronized void gotImageError(Object imageSrc, String msg) { if (imageSrc != mPreviewData) { return; } else { displayImage(NoImage); if (msg != null) status("Error: " + msg); isLoading = false; } } /* private void displayIcon(ImageIcon icon) { displayImage(icon.getImage()); } */ private void displayImage(Image image) { if (DEBUG.RESOURCE || DEBUG.IMAGE) out("displayImage " + Util.tags(image)); mImage = image; if (mImage != null) { mImageWidth = mImage.getWidth(null); mImageHeight = mImage.getHeight(null); if (DEBUG.IMAGE) out("displayImage " + Util.tags(image) + "; " + mImageWidth + "x" + mImageHeight); } clearStatus(); repaint(); } public void run() { loadPreview(mPreviewData); } private void out(String s) { Log.debug(s); } /** draw the image into the current avilable space, scaling it down if needed (never scale up tho) */ @Override public void paintComponent(Graphics g) { if (DEBUG.IMAGE) out("paint"); if (mImage == null) { if (LazyLoadOnPaint) { if (!isLoading && mPreviewData != null) { // TODO: fix this double-checked locking (not a safe idiom) // don't even try LazyLoadOnPaint until this is fixed synchronized (this) { if (!isLoading && mPreviewData != null) VUE.invokeAfterAWT(PreviewPane.this); // load the preview } } } return; } //g.setColor(Color.black); //g.fillRect(0,0, w,h); double zoomFit; double netZoom; if (mImage == NoImage) { zoomFit = 1; netZoom = 1; } else { java.awt.geom.Rectangle2D imageBounds = new java.awt.geom.Rectangle2D.Float(0, 0, mImageWidth, mImageHeight); zoomFit = ZoomTool.computeZoomFit(getSize(), 0, imageBounds, null, false); final double maxZoom; if (mImageWidth == 128 && mImageHeight == 128) maxZoom = 1; // if looks like an icon, don't scale else if (getHeight() > MinHeight) maxZoom = 2; // if preview has been expanded, allow to scale up else maxZoom = 1; // default: scale never greater than 100% if (zoomFit > 1 && zoomFit < 2) netZoom = 1; // if (zoomFit > 1 && zoomFit <= 1.25) // netZoom = 1; // else if (zoomFit > 2 && zoomFit < 3) // netZoom = 2; else if (zoomFit > maxZoom) netZoom = maxZoom; else netZoom = zoomFit; } final int drawW = (int) (mImageWidth * netZoom); final int drawH = (int) (mImageHeight * netZoom); final int w = getWidth(); final int h = getHeight(); // center if drawable area is bigger than image int xoff = 0; int yoff = 0; if (drawW != w) xoff = (w - drawW) / 2; if (drawH != h) yoff = (h - drawH) / 2; //if (DEBUG.IMAGE) out("painting " + Util.tag(mImage) + "; into " + drawW + "x" + drawH); if (DEBUG.IMAGE) out(String.format("painting %s into %dx%d; zoomFit=%.1f%%; netZoom=%.1f%%;", Util.tags(mImage), drawW, drawH, zoomFit*100, netZoom*100)); g.drawImage(mImage, xoff, yoff, drawW, drawH, null); } }