com.actelion.research.gui.JImagePanel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openchemlib Show documentation
Show all versions of openchemlib Show documentation
Open Source Chemistry Library
/*
* Copyright 2017 Idorsia Pharmaceuticals Ltd., Hegenheimermattweg 91, CH-4123 Allschwil, Switzerland
*
* This file is part of DataWarrior.
*
* DataWarrior is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* DataWarrior is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with DataWarrior.
* If not, see http://www.gnu.org/licenses/.
*
* @author Thomas Sander
*/
package com.actelion.research.gui;
import com.actelion.research.gui.clipboard.ImageClipboardHandler;
import com.actelion.research.util.CursorHelper;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.ImageObserver;
public class JImagePanel extends JPanel implements ActionListener,ImageObserver,KeyListener,MouseListener,MouseMotionListener,MouseWheelListener {
private static final long serialVersionUID = 0x20120502;
public static final Color BACKGROUND_COLOR = UIManager.getColor("Panel.background");
public static final String cDefaultImageExtension = ".jpg";
public static final String cThumbNailExtension = "_s"; // inserted before file extension in case of thumbnails
private static final int IMAGE_ERROR = -1;
private static final int IMAGE_NO_IMAGE = 0;
private static final int IMAGE_PENDING = 1;
private static final int IMAGE_LOADING = 2;
private static final int IMAGE_AVAILABLE = 3;
private static final int MIN_SELECTION_PIXELS = 8;
private static final float MAX_ZOOM_FACTOR = 8f;
private String mImagePath,mFileName;
private Image mImage,mLowResImage;
private byte[] mImageData;
private boolean mImageIsThumbNail,mUseThumbNail,mAltIsDown,mMouseIsDown,mMouseIsInside;
private int mImageStatus,mImageCenterOffsetX,mImageCenterOffsetY,mCurrentCursor,mImageUpdateCount;
private float mZoomFactor;
private Point mMouseLocation;
private Rectangle mSelectionRect,mImageRect;
private ImageDataSource mImageDataSource;
private PopupItemProvider mPopupItemProvider;
public JImagePanel() {
this("", false);
}
public JImagePanel(String imagePath) {
this(imagePath, false);
}
public JImagePanel(String imagePath, boolean useThumbNail) {
setFocusable(true);
addKeyListener(this);
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
mImagePath = (imagePath == null) ? "" : imagePath;
mUseThumbNail = useThumbNail;
mImageRect = new Rectangle();
resetZoomState();
mCurrentCursor = CursorHelper.cPointerCursor;
}
/**
* If this panel uses thumbnails and if this panel is not based on path names,
* then use this method to define the source for high resolution image data.
* @param source
*/
public void setHighResolutionImageSource(ImageDataSource source) {
mImageDataSource = source;
}
/**
* Use this, if you want the popup menu to contain more than just "Copy Full Image"
* and "Copy Visible".
* @param pip
*/
public void setPopupItemProvider(PopupItemProvider pip) {
mPopupItemProvider = pip;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension theSize = getSize();
if (theSize.width == 0 || theSize.height == 0)
return;
g.setColor(BACKGROUND_COLOR);
g.fillRect(0, 0, theSize.width, theSize.height);
if (mImageStatus == IMAGE_PENDING) {
if (mImageData != null || mFileName != null) {
createAndPrepareImage(); // changes status to IMAGE_LOADING or IMAGE_AVAILABLE
}
else {
mImage = null;
mImageStatus = IMAGE_NO_IMAGE;
}
}
if (mImage != null) {
if (mImageStatus == IMAGE_LOADING) {
if (mLowResImage != null) // if we have a thumbnail
drawImage(g, mLowResImage);
String message = (mUseThumbNail && !mImageIsThumbNail) ?
"higher resolution loading..." : "image loading...";
g.setColor(Color.blue);
g.drawString(message, 4, theSize.height - 4);
}
else if (mImageStatus == IMAGE_ERROR) {
g.setColor(Color.red);
g.drawString("image loading error.", 4, theSize.height - 4);
}
else if (mImageStatus == IMAGE_AVAILABLE) {
drawImage(g, mImage);
}
}
if (mSelectionRect != null) {
g.setColor(mSelectionRect.width >= MIN_SELECTION_PIXELS
&& mSelectionRect.height >= MIN_SELECTION_PIXELS ? Color.GREEN : Color.RED);
g.drawRect(mSelectionRect.x-1, mSelectionRect.y-1, mSelectionRect.width+2, mSelectionRect.height+2);
g.drawRect(mSelectionRect.x-2, mSelectionRect.y-2, mSelectionRect.width+4, mSelectionRect.height+4);
}
}
private void drawImage(Graphics g, Image image) {
if (image.getWidth(this) > 0 && image.getHeight(this) > 0) {
if (mZoomFactor == 1f) {
mImageRect.x = mImageCenterOffsetX + (getWidth() - image.getWidth(this)) / 2;
mImageRect.y = mImageCenterOffsetY + (getHeight() - image.getHeight(this)) / 2;
mImageRect.width = image.getWidth(this);
mImageRect.height = image.getHeight(this);
g.drawImage(image, mImageRect.x, mImageRect.y, this);
}
else {
mImageRect.width = (int)(image.getWidth(this)*mZoomFactor);
mImageRect.height = (int)(image.getHeight(this)*mZoomFactor);
mImageRect.x = mImageCenterOffsetX + (getWidth() - mImageRect.width) / 2;
mImageRect.y = mImageCenterOffsetY + (getHeight() - mImageRect.height) / 2;
g.drawImage(image, mImageRect.x, mImageRect.y, mImageRect.width, mImageRect.height, this);
}
}
}
public boolean imageUpdate(Image img, final int flags, int x, int y, int width, int height) {
if (((ImageObserver.ALLBITS
| ImageObserver.ERROR
| ImageObserver.ABORT) & flags) != 0) {
final int request = ++mImageUpdateCount;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (mImage != null && request == mImageUpdateCount) { // check whether this is the latest request
mImageStatus = ((ImageObserver.ALLBITS & flags) == 0) ? IMAGE_ERROR : IMAGE_AVAILABLE;
if (mImageStatus == IMAGE_AVAILABLE)
setInitialFullImageZoomState();
repaint();
}
}
} );
return false;
}
return true;
}
/**
* If this uses thumbnails and if you use this method to provide the thumbnail,
* then you must also define a source for high resolution images with
* setHighResolutionImageSource().
* @param imageData
*/
public void setImageData(byte[] imageData) {
mFileName = null; // other potential source for image information
if (imageData == mImageData)
return; // no change
mImageData = imageData;
mImageStatus = IMAGE_PENDING;
mImageIsThumbNail = mUseThumbNail;
mSelectionRect = null;
mLowResImage = null;
resetZoomState();
repaint();
}
public void setFileName(String fileName) {
mImageData = null; // other potential source for image information
if (fileName != null && fileName.length() == 0)
fileName = null;
if (mFileName == null && fileName == null)
return;
if (mFileName != null && fileName != null && mFileName.equals(fileName))
return;
mFileName = fileName;
mImageStatus = IMAGE_PENDING;
mImageIsThumbNail = mUseThumbNail;
mSelectionRect = null;
mLowResImage = null;
resetZoomState();
repaint();
}
private void createAndPrepareImage() {
if (mImageData != null) {
mImage = Toolkit.getDefaultToolkit().createImage(mImageData);
}
else if (mFileName != null) {
String filePath = buildImagePath(mImagePath, mFileName, mUseThumbNail);
mImage = Toolkit.getDefaultToolkit().createImage(filePath);
}
if (Toolkit.getDefaultToolkit().prepareImage(mImage, -1, -1, this))
mImageStatus = IMAGE_AVAILABLE;
else
mImageStatus = IMAGE_LOADING;
if (mImageStatus == IMAGE_AVAILABLE)
setInitialFullImageZoomState();
}
public static String buildImagePath(String path, String name, boolean useThumbNail) {
String fileExtension = cDefaultImageExtension;
// in case the file name has already an image-type extension, then use that instead of cLargeImageExtension
int index = name.lastIndexOf('.');
if (index != -1 && index >= name.length()-5 && index <= name.length()-4) {
String extension = name.substring(index+1).toLowerCase();
if (extension.equals("jpeg") || extension.equals("jpg") || extension.equals("png") || extension.equals("gif") || extension.equals("svg")) {
fileExtension = name.substring(index);
name = name.substring(0, index);
}
}
String thumbExtension = useThumbNail ? cThumbNailExtension : "";
return path + name + thumbExtension + fileExtension;
}
private void loadFullImage() {
mImageIsThumbNail = false;
mLowResImage = mImage;
mImageStatus = IMAGE_LOADING;
repaint();
if (mImageData != null) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
mImageData = mImageDataSource.getImageData();
createAndPrepareImage();
}
} );
}
/* mImageIsThumbNail = false;
if (mImageData != null)
mImageData = mImageDataSource.getImageData();
mLowResImage = mImage;
createAndPrepareImage(); */
}
public void setImagePath(String imagePath) {
mImagePath = (imagePath == null) ? "" : imagePath;
mFileName = null;
mImageData = null;
mImageStatus = IMAGE_NO_IMAGE;
repaint();
}
public String getImagePath() {
return mImagePath;
}
public void setUseThumbNail(boolean value) {
mUseThumbNail = value;
}
public boolean usesThumbNail() {
return mUseThumbNail;
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ALT) {
mAltIsDown = true;
updateCursor();
}
}
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ALT) {
mAltIsDown = false;
updateCursor();
}
if ((e.getModifiers() & Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0) {
if (e.getKeyCode() == KeyEvent.VK_C)
copyVisible();
}
}
public void keyTyped(KeyEvent e) {}
public void mouseClicked(MouseEvent e) {}
public void mousePressed(MouseEvent e) {
if (handlePopupTrigger(e))
return;
mMouseIsDown = true;
mSelectionRect = null;
if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0 && mImageStatus == IMAGE_AVAILABLE) {
mMouseLocation = e.getPoint();
if (mAltIsDown) // start a rectangular selection
mSelectionRect = new Rectangle(e.getX(), e.getY(), 0, 0);
}
updateCursor();
repaint();
}
public void mouseReleased(MouseEvent e) {
if (handlePopupTrigger(e))
return;
mMouseIsDown = false;
updateCursor();
if (mSelectionRect != null) {
Rectangle sharedRect = mSelectionRect.intersection(mImageRect);
if (sharedRect.width >= MIN_SELECTION_PIXELS
&& sharedRect.height >= MIN_SELECTION_PIXELS) {
if (mImageIsThumbNail)
loadFullImage();
float factor = Math.min(MAX_ZOOM_FACTOR / mZoomFactor,
Math.min((float)getWidth() / (float)sharedRect.width,
(float)getHeight() / (float)sharedRect.height));
if (factor > 1.000001) {
mImageCenterOffsetX = (int)(factor * (mImageRect.x-sharedRect.x+(mImageRect.width-sharedRect.width)/2));
mImageCenterOffsetY = (int)(factor * (mImageRect.y-sharedRect.y+(mImageRect.height-sharedRect.height)/2));
mZoomFactor *= factor;
}
}
mSelectionRect = null;
repaint();
}
}
public void mouseEntered(MouseEvent e) {
requestFocusInWindow();
mMouseIsInside = true;
updateCursor();
}
public void mouseExited(MouseEvent e) {
mMouseIsInside = false;
}
public synchronized void mouseMoved(MouseEvent e) {}
public synchronized void mouseDragged(MouseEvent e) {
if (mSelectionRect != null) {
mSelectionRect.x = Math.min(e.getX(), mMouseLocation.x);
mSelectionRect.y = Math.min(e.getY(), mMouseLocation.y);
mSelectionRect.width = Math.abs(mMouseLocation.x - e.getX());
mSelectionRect.height = Math.abs(mMouseLocation.y - e.getY());
repaint();
}
else if (mImageStatus == IMAGE_AVAILABLE) {
int dx = e.getX() - mMouseLocation.x;
int dy = e.getY() - mMouseLocation.y;
mImageCenterOffsetX += dx;
mImageCenterOffsetY += dy;
mMouseLocation.x += dx;
mMouseLocation.y += dy;
validateOffsets();
repaint();
}
}
public void mouseWheelMoved(MouseWheelEvent e) {
if (mImageStatus == IMAGE_AVAILABLE) {
zoom(e.getX(), e.getY(), e.getWheelRotation());
if (mImageIsThumbNail)
loadFullImage();
}
}
private void zoom(int sx, int sy, int steps) {
float f = (float)Math.exp(-steps / 20.0);
float fullScaleFactor = Math.min((float)getWidth() / (float)mImage.getWidth(this), (float)getHeight() / (float)mImage.getHeight(this));
float minZoomFactor = Math.min(fullScaleFactor, Math.min(mZoomFactor, 1f));
float newZoomFactor = Math.max(minZoomFactor, Math.min(MAX_ZOOM_FACTOR, f*mZoomFactor));
if (mZoomFactor != newZoomFactor) {
int oldZoomedImageWidth = (int)(mImage.getWidth(this)*mZoomFactor);
int oldZoomedImageHeight = (int)(mImage.getHeight(this)*mZoomFactor);
float relX = Math.max(0f, Math.min(1f, (sx-mImageCenterOffsetX+(oldZoomedImageWidth-getWidth())/2f)/oldZoomedImageWidth));
float relY = Math.max(0f, Math.min(1f, (sy-mImageCenterOffsetY+(oldZoomedImageHeight-getHeight())/2f)/oldZoomedImageHeight));
mZoomFactor = newZoomFactor;
mImageCenterOffsetX += (0.5f-relX)*((int)(mImage.getWidth(this)*mZoomFactor)-oldZoomedImageWidth);
mImageCenterOffsetY += (0.5f-relY)*((int)(mImage.getHeight(this)*mZoomFactor)-oldZoomedImageHeight);
validateOffsets();
repaint();
}
}
private void validateOffsets() {
int zoomedImageWidth = (int)(mImage.getWidth(this)*mZoomFactor);
int zoomedImageHeight = (int)(mImage.getHeight(this)*mZoomFactor);
int horizontalOversize = (zoomedImageWidth-getWidth())/2;
int verticalOversize = (zoomedImageHeight-getHeight())/2;
int leftWhiteSpace = mImageCenterOffsetX - horizontalOversize;
int rightWhiteSpace = -mImageCenterOffsetX - horizontalOversize;
int topWhiteSpace = mImageCenterOffsetY - verticalOversize;
int bottomWhiteSpace = -mImageCenterOffsetY - verticalOversize;
if (horizontalOversize < 0) {
if (leftWhiteSpace < 0)
mImageCenterOffsetX -= leftWhiteSpace;
else if (rightWhiteSpace < 0)
mImageCenterOffsetX += rightWhiteSpace;
}
else {
if (leftWhiteSpace > 0)
mImageCenterOffsetX -= leftWhiteSpace;
else if (rightWhiteSpace > 0)
mImageCenterOffsetX += rightWhiteSpace;
}
if (verticalOversize < 0) {
if (topWhiteSpace < 0)
mImageCenterOffsetY -= topWhiteSpace;
else if (bottomWhiteSpace < 0)
mImageCenterOffsetY += bottomWhiteSpace;
}
else {
if (topWhiteSpace > 0)
mImageCenterOffsetY -= topWhiteSpace;
else if (bottomWhiteSpace > 0)
mImageCenterOffsetY += bottomWhiteSpace;
}
}
private void resetZoomState() {
mZoomFactor = 1f;
mImageCenterOffsetX = 0;
mImageCenterOffsetY = 0;
}
private void setInitialFullImageZoomState() {
if (mLowResImage != null) { // translate zoom from thumbnail to high-res image
mZoomFactor *= (float)mLowResImage.getWidth(null) / (float)mImage.getWidth(null);
mLowResImage = null; // use high-res image from now on
}
else {
Dimension theSize = getSize();
if (theSize.width != 0 && theSize.height != 0)
mZoomFactor = Math.min(1f, Math.min((float)theSize.width/(float)mImage.getWidth(null),
(float)theSize.height/(float)mImage.getHeight(null)));
mImageCenterOffsetX = 0;
mImageCenterOffsetY = 0;
}
}
private boolean handlePopupTrigger(MouseEvent e) {
if (e.isPopupTrigger()) {
if (mImageStatus == IMAGE_AVAILABLE) {
JPopupMenu popup = new JPopupMenu();
JMenuItem item1 = new JMenuItem("Copy Full Image");
item1.addActionListener(this);
popup.add(item1);
JMenuItem item2 = new JMenuItem("Copy Visible");
item2.addActionListener(this);
popup.add(item2);
if (mPopupItemProvider != null) {
JMenuItem[] extendedItemList = mPopupItemProvider.getPopupItems();
if (extendedItemList != null) {
popup.addSeparator();
for (JMenuItem item:extendedItemList)
popup.add(item);
}
}
popup.show(this, e.getX(), e.getY());
}
return true;
}
return false;
}
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("Copy Full Image")) {
copyImage();
return;
}
if (e.getActionCommand().equals("Copy Visible")) {
copyVisible();
return;
}
}
private void copyImage() {
if (mImageStatus == IMAGE_AVAILABLE)
ImageClipboardHandler.copyImage(mImage);
}
private void copyVisible() {
if (mImageStatus == IMAGE_AVAILABLE) {
Rectangle visRect = mImageRect.intersection(new Rectangle(0, 0, getWidth(), getHeight()));
if (!visRect.isEmpty()) {
int visWidth = (int)((float)visRect.width / mZoomFactor);
int visHeight = (int)((float)visRect.height / mZoomFactor);
Image image = createImage(visWidth, visHeight);
int x = (int)((float)(mImageRect.x-visRect.x)/mZoomFactor);
int y = (int)((float)(mImageRect.y-visRect.y)/mZoomFactor);
image.getGraphics().drawImage(mImage, x, y, this);
ImageClipboardHandler.copyImage(image);
}
}
}
private void updateCursor() {
if (mMouseIsInside) {
int cursor = CursorHelper.cPointerCursor;
if (mImageStatus == IMAGE_AVAILABLE) {
if (mAltIsDown)
cursor = CursorHelper.cSelectRectCursor;
else
cursor = mMouseIsDown ? CursorHelper.cFistCursor : CursorHelper.cHandCursor;
}
if (mCurrentCursor != cursor) {
mCurrentCursor = cursor;
setCursor(CursorHelper.getCursor(cursor));
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy