/*
* Copyright 2016 Igor Maznitsa.
*
* 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.igormaznitsa.sciareto.ui.editors;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.filechooser.FileFilter;
import org.apache.commons.io.FilenameUtils;
import com.igormaznitsa.mindmap.model.logger.Logger;
import com.igormaznitsa.mindmap.model.logger.LoggerFactory;
import com.igormaznitsa.mindmap.swing.panel.MindMapPanelConfig;
import com.igormaznitsa.sciareto.Context;
import com.igormaznitsa.sciareto.preferences.PreferencesManager;
import com.igormaznitsa.sciareto.ui.tabs.TabTitle;
import com.igormaznitsa.sciareto.ui.FindTextScopeProvider;
public final class PictureViewer extends AbstractEditor {
private static final Logger LOGGER = LoggerFactory.getLogger(PictureViewer.class);
private final TabTitle title;
private transient BufferedImage image;
public static final Set<String> SUPPORTED_FORMATS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("png", "jpg", "gif"))); //NOI18N
private final JScrollPane scrollPane = new JScrollPane();
private static final class ScalableImage extends JComponent {
private static final long serialVersionUID = 6804581090800919466L;
private BufferedImage image;
private float scale = 1.0f;
private static final float SCALE_STEP = 0.2f;
private final MindMapPanelConfig config = new MindMapPanelConfig();
public ScalableImage(){
super();
this.addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseWheelMoved(@Nonnull final MouseWheelEvent e) {
if (!e.isConsumed() && ((e.getModifiers() & config.getScaleModifiers()) == config.getScaleModifiers())) {
scale = Math.max(0.2f, Math.min(scale + (SCALE_STEP * -e.getWheelRotation()), 10.0f));
revalidate();
repaint();
}
}
});
}
private void updateConfig(){
this.config.loadFrom(PreferencesManager.getInstance().getPreferences());
}
public MindMapPanelConfig getConfig(){
return this.config;
}
@Override
@Nonnull
public Dimension getMinimumSize() {
return getPreferredSize();
}
@Override
@Nonnull
public Dimension getMaximumSize() {
return getPreferredSize();
}
@Override
@Nonnull
public Dimension getPreferredSize() {
if (image == null) {
return new Dimension(16, 16);
} else {
return new Dimension(Math.round(this.image.getWidth() * this.scale), Math.round(this.image.getHeight() * this.scale));
}
}
@Override
public void paintComponent(@Nonnull final Graphics g){
final Graphics2D gfx = (Graphics2D) g;
gfx.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
gfx.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BICUBIC);
gfx.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
final Rectangle bounds = this.getBounds();
if (this.image == null){
gfx.setColor(Color.BLACK);
gfx.fillRect(0,0, bounds.width, bounds.height);
gfx.setColor(Color.RED);
final String text = "Can't load image, check the log!";
gfx.drawString(text, (bounds.width-gfx.getFontMetrics().stringWidth(text))/2, (bounds.height - gfx.getFontMetrics().getMaxAscent()) / 2);
}else{
final Dimension size = getPreferredSize();
gfx.drawImage(this.image, Math.max(0,(bounds.width-size.width)/2), Math.max(0, (bounds.height - size.height) / 2), size.width, size.height, null);
}
}
public void setImage(@Nullable final BufferedImage image){
this.image = image;
this.scale = 1.0f;
revalidate();
repaint();
}
}
private final ScalableImage imageViewer;
@Override
public void focusToEditor() {
}
public static final FileFilter IMAGE_FILE_FILTER = new FileFilter() {
@Override
public boolean accept(@Nonnull final File f) {
if (f.isDirectory()) {
return true;
}
final String ext = FilenameUtils.getExtension(f.getName()).toLowerCase(Locale.ENGLISH);
return SUPPORTED_FORMATS.contains(ext);
}
@Override
@Nonnull
public String getDescription() {
return "Image file (*.png,*.jpg,*.gif)";
}
};
@Override
@Nonnull
public FileFilter getFileFilter() {
return IMAGE_FILE_FILTER;
}
public PictureViewer(@Nonnull final Context context, @Nonnull final File file) throws IOException {
super();
this.title = new TabTitle(context, this, file);
this.imageViewer = new ScalableImage();
loadContent(file);
}
@Override
public void loadContent(@Nullable final File file) throws IOException {
BufferedImage loaded = null;
if (file != null) {
try {
loaded = ImageIO.read(file);
} catch (Exception ex) {
LOGGER.error("Can't load image", ex); //NOI18N
loaded = null;
}
}
this.image = loaded;
this.imageViewer.setImage(this.image);
this.scrollPane.setViewportView(this.imageViewer);
this.scrollPane.revalidate();
}
@Override
public boolean saveDocument() throws IOException {
boolean result = false;
final File docFile = this.title.getAssociatedFile();
if (docFile != null) {
final String ext = FilenameUtils.getExtension(docFile.getName()).trim().toLowerCase(Locale.ENGLISH);
if (SUPPORTED_FORMATS.contains(ext)) {
try {
ImageIO.write(this.image, ext, docFile);
result = true;
} catch (Exception ex) {
if (ex instanceof IOException) {
throw (IOException) ex;
}
throw new IOException("Can't write image", ex); //NOI18N
}
} else {
try {
LOGGER.warn("unsupported image format, will be saved as png : " + ext); //NOI18N
ImageIO.write(this.image, "png", docFile); //NOI18N
result = true;
} catch (Exception ex) {
if (ex instanceof IOException) {
throw (IOException) ex;
}
throw new IOException("Can't write image", ex); //NOI18N
}
}
}
return result;
}
@Override
public void updateConfiguration() {
this.imageViewer.updateConfig();
this.scrollPane.revalidate();
this.scrollPane.repaint();
}
@Override
public boolean isEditable() {
return false;
}
@Override
public boolean isSaveable() {
return false;
}
@Override
@Nonnull
public TabTitle getTabTitle() {
return this.title;
}
@Override
@Nonnull
public EditorContentType getEditorContentType() {
return EditorContentType.IMAGE;
}
@Override
@Nonnull
public JComponent getMainComponent() {
return this.imageViewer;
}
@Override
@Nonnull
public JComponent getContainerToShow() {
return this.scrollPane;
}
@Override
@Nonnull
public AbstractEditor getEditor() {
return this;
}
@Override
public boolean isRedo() {
return false;
}
@Override
public boolean isUndo() {
return false;
}
@Override
public boolean redo() {
return false;
}
@Override
public boolean undo() {
return false;
}
@Override
public boolean findNext(@Nonnull final Pattern pattern, @Nonnull final FindTextScopeProvider provider) {
return false;
}
@Override
public boolean findPrev(@Nonnull final Pattern pattern, @Nonnull final FindTextScopeProvider provider) {
return false;
}
@Override
public boolean doesSupportPatternSearch() {
return false;
}
@Override
public boolean doesSupportCutCopyPaste() {
return false;
}
@Override
public boolean isCutAllowed() {
return false;
}
@Override
public boolean doCut() {
return false;
}
@Override
public boolean isCopyAllowed() {
return false;
}
@Override
public boolean isPasteAllowed() {
return false;
}
@Override
public boolean doCopy() {
return false;
}
@Override
public boolean doPaste() {
return false;
}
}