package org.geogebra.desktop.gui.util;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Label;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
import org.geogebra.common.io.MyXMLio;
import org.geogebra.common.util.Charsets;
import org.geogebra.common.util.debug.Log;
import org.geogebra.desktop.awt.GGraphics2DD;
import org.geogebra.desktop.gui.MyImageD;
import org.geogebra.desktop.io.MyXMLioD;
import org.geogebra.desktop.main.AppD;
import org.geogebra.desktop.util.UtilD;
/**
* An enhanced file chooser for GeoGebra which can be used to load images or ggb
* files with a preview image.
*
* @author Florian Sonner
* @version 1.0
*/
public class GeoGebraFileChooser extends JFileChooser
implements ComponentListener {
private static final long serialVersionUID = 1L;
/**
* The with at which the accessory is displayed.
*/
private static final int ACCESSORY_WIDTH = 600;
/**
* The file chooser is used to load images at the moment.
*/
public static final int MODE_IMAGES = 0;
/**
* The file chooser is used to load ggb files at the moment.
*/
public static final int MODE_GEOGEBRA = 1;
/**
* The file chooser is used to save ggb files at the moment.
*/
public static final int MODE_GEOGEBRA_SAVE = 2;
/**
* The file chooser is used to load data files at the moment.
*/
public static final int MODE_DATA = 3;
/**
* An instance of the GeoGebra application.
*/
AppD app;
/**
* The current mode of the file chooser.
*/
private int currentMode = -1;
/**
* The accessory panel which displays the preview of the selected file.
*/
private PreviewPanel previewPanel;
/**
* Whether to show the accessory or not. The accessory is not displayed in
* case the dialog is too small.
*/
private boolean showAccessory = true;
/**
* Construct a file chooser without a restricted file system view.
*
* May throw IOException: Could not get shell folder ID list (Java bug
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6544857)
*
* If an exception is catched, the constructor with a restricted file system
* view should be used.
*
* @param app
* applivcation
*
* @param currentDirectory
* directory
*/
public GeoGebraFileChooser(AppD app, File currentDirectory) {
this(app, currentDirectory, false);
}
/**
* Construct a file chooser which may have a restricted file system view if
* the second parameter is set to true.
*
* @param app
* application
*
* @param currentDirectory
* directory
* @param restricted
* whether to use RestrictedFileSystemView
*/
public GeoGebraFileChooser(AppD app, File currentDirectory,
boolean restricted) {
super(currentDirectory,
(restricted ? new RestrictedFileSystemView() : null));
this.app = app;
previewPanel = new PreviewPanel(this);
setAccessory(previewPanel);
addPropertyChangeListener(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY,
previewPanel);
addComponentListener(this);
setMode(MODE_GEOGEBRA); // default mode is the mode to load geogebra
// files
Dimension d = this.getPreferredSize();
d.width = Math.max(ACCESSORY_WIDTH, d.width);
this.setPreferredSize(d);
}
/**
* Get the current mode of the file chooser.
*
* @return current mode
*/
public int getMode() {
return currentMode;
}
/**
* Set a new mode for the file chooser. Use the constants defined in this
* class for the different modes.
*
* @param mode0
* file selection mode
*/
public void setMode(int mode0) {
// invalid mode?
int mode = mode0;
if (mode != MODE_IMAGES && mode != MODE_GEOGEBRA
&& mode != MODE_GEOGEBRA_SAVE && mode != MODE_DATA) {
Log.debug(
"Invalid file chooser mode, MODE_GEOGEBRA used as default.");
mode = MODE_GEOGEBRA;
}
// do not perform any unnecessary actions
if (this.currentMode == mode) {
return;
}
if (mode == MODE_GEOGEBRA) { // load/save ggb, ggt etc. files
setMultiSelectionEnabled(true);
} else { // load images
setMultiSelectionEnabled(false);
}
// set the preview panel type: image, data or ?
previewPanel.setPreviewPanelType(mode);
// TODO apply mode specific settings..
this.currentMode = mode;
}
/**
* Check if we have to show / hide the accessory
*/
@Override
public void componentResized(ComponentEvent e) {
if (getSize().width < ACCESSORY_WIDTH && showAccessory) {
setAccessory(null);
removePropertyChangeListener(
JFileChooser.SELECTED_FILE_CHANGED_PROPERTY, previewPanel);
showAccessory = false;
validate();
} else if (getSize().width > ACCESSORY_WIDTH && !showAccessory) {
setAccessory(previewPanel);
addPropertyChangeListener(
JFileChooser.SELECTED_FILE_CHANGED_PROPERTY, previewPanel);
// fire an event that the selected file has changed to update the
// preview image
previewPanel.propertyChange(null);
showAccessory = true;
validate();
}
}
@Override
public void componentShown(ComponentEvent e) {
// nothing to do
}
@Override
public void componentHidden(ComponentEvent e) {
// nothing to do
}
@Override
public void componentMoved(ComponentEvent e) {
// nothing to do
}
/**
* Component to preview image files in a file chooser.
*
* This file is based on Hack #31 in
* "Swing Hacks - Tips & Tools for Building Killer GUIs" by Joshua Marinacci
* and Chris Adamson.
*
* Modified & commented by Florian Sonner for GeoGebraFileChooser
*
* @author Joshua Marinacci
* @author Chris Adamson
* @author Philipp Weissenbacher (materthron@users.sourceforge.net)
* @author Florian Sonner
*/
private class PreviewPanel extends JPanel
implements PropertyChangeListener {
private static final long serialVersionUID = 1L;
/**
* The maximum file size of images in kb.
*/
private static final int maxImageSize = 300;
private GeoGebraFileChooser fileChooser;
/**
* The image to draw in the preview area.
*/
private MyImageD img = null;
/**
* The panel on which the image is drawn.
*/
private ImagePanel imagePanel = null;
/**
* Panel for data preview.
*/
private JTextArea dataPreviewPanel = null;
/**
* CardLayout panel to hold different preview panels
*/
private JPanel cards;
/**
* A label to describe the properties of the selected file.
*/
private Label fileLabel;
public PreviewPanel(GeoGebraFileChooser fileChooser) {
this.fileChooser = fileChooser;
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5)); // border at
// the left
// & right
imagePanel = new ImagePanel();
cards = new JPanel(new CardLayout());
cards.add("imagePanel", imagePanel);
cards.add("dataPanel", buildDataPanel());
add(BorderLayout.CENTER, cards);
fileLabel = new Label();
add(BorderLayout.SOUTH, fileLabel);
}
public void setPreviewPanelType(int mode) {
CardLayout layout = (CardLayout) cards.getLayout();
if (mode == GeoGebraFileChooser.MODE_DATA) {
layout.show(cards, "dataPanel");
dataPreviewPanel.setText(
app.getLocalization().getMenu("PreviewUnavailable"));
} else {
layout.show(cards, "imagePanel");
setImg(null);
}
fileLabel.setText(null);
}
public JScrollPane buildDataPanel() {
dataPreviewPanel = new JTextArea();
dataPreviewPanel.setEditable(false);
dataPreviewPanel.setWrapStyleWord(false);
dataPreviewPanel.setLineWrap(false);
dataPreviewPanel.setPreferredSize(imagePanel.getPreferredSize());
dataPreviewPanel.setMargin(new Insets(5, 5, 5, 5));
dataPreviewPanel.setText(
app.getLocalization().getMenu("PreviewUnavailable"));
JScrollPane scroller = new JScrollPane(dataPreviewPanel);
scroller.setHorizontalScrollBarPolicy(
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scroller.setVerticalScrollBarPolicy(
ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
return scroller;
}
/**
* A new file was selected -> update the panel if necessary.
*/
@Override
public void propertyChange(PropertyChangeEvent evt) {
try {
File file = fileChooser.getSelectedFile();
if (file != null && file.exists()) {
// change
if (fileChooser.getMode() == GeoGebraFileChooser.MODE_DATA) {
updateDataPreview(file);
} else {
updateImage(file);
}
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
/**
* Updates the data preview panel
*
* @param file
* @throws IOException
*/
private void updateDataPreview(File file) throws IOException {
fileLabel.setText("");
String fileName = file.getName();
StringBuffer contents = new StringBuffer();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(
new FileInputStream(file), Charsets.UTF_8));
String text = null;
int lineCount = 0;
// read at most 20 lines
while ((text = reader.readLine()) != null && lineCount < 20) {
contents.append(text)
.append(System.getProperty("line.separator"));
lineCount++;
}
StringBuilder fileInfo = new StringBuilder();
if (fileName.length() > 20) {
fileInfo.append(fileName.substring(0, 20));
fileInfo.append("..");
} else {
fileInfo.append(fileName);
}
fileLabel.setText(fileInfo.toString());
if (contents.length() == 0) {
contents.append(app.getLocalization()
.getMenu("PreviewUnavailable"));
}
dataPreviewPanel.setText(contents.toString());
dataPreviewPanel.setCaretPosition(0);
}
catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Update the preview image if it's possible to load one.
*
* @param file
* @throws IOException
*/
private void updateImage(File file) throws IOException {
fileLabel.setText("");
try {
MyImageD tmpImage = null;
String fileName = file.getName();
// Update preview for opening ggb files
if (fileChooser
.getMode() == GeoGebraFileChooser.MODE_GEOGEBRA) {
if (fileName.endsWith(".ggb")) {
tmpImage = new MyImageD(MyXMLioD.getPreviewImage(file)); // load
// preview
// from
// zip
StringBuilder fileInfo = new StringBuilder();
if (fileName.length() > 20) {
fileInfo.append(fileName.substring(0, 20));
fileInfo.append("..");
} else {
fileInfo.append(fileName);
}
fileInfo.append(" : ");
fileInfo.append(file.length() / 1024);
fileInfo.append(" kB");
fileLabel.setText(fileInfo.toString());
}
}
// Update preview for saving a ggb file
else if (fileChooser
.getMode() == GeoGebraFileChooser.MODE_GEOGEBRA_SAVE) {
tmpImage = new MyImageD(
app.getExportImage(MyXMLio.THUMBNAIL_PIXELS_X,
MyXMLio.THUMBNAIL_PIXELS_Y));
// TODO: show file size info?
fileLabel.setText(null);
}
// Update preview for images
else {
// fails for a few JPEGs so turn off preview for large files
if (file.length() < 1024 * maxImageSize) {
if (fileName.endsWith(".svg")) {
FileInputStream is = new FileInputStream(file);
String svg = UtilD
.loadIntoString(is);
is.close();
tmpImage = new MyImageD(svg, fileName);
} else {
// returns null if the file isn't an image
BufferedImage bi = ImageIO.read(file);
if (bi != null) {
tmpImage = new MyImageD(bi);
}
}
StringBuilder imgInfo = new StringBuilder();
if (fileName.length() > 20) {
imgInfo.append(fileName.substring(0, 20));
imgInfo.append("..");
} else {
imgInfo.append(fileName);
}
if (tmpImage != null) {
imgInfo.append(" : ");
imgInfo.append(tmpImage.getWidth());
imgInfo.append(" x ");
imgInfo.append(tmpImage.getHeight());
}
fileLabel.setText(imgInfo.toString());
}
}
// resize tmp image if necessary
if (tmpImage != null) {
int oldWidth = tmpImage.getWidth();
int oldHeight = tmpImage.getHeight();
int newWidth;
int newHeight;
if (oldWidth > ImagePanel.SIZE
|| oldHeight > ImagePanel.SIZE) {
if (oldWidth > oldHeight) {
newWidth = ImagePanel.SIZE;
newHeight = (ImagePanel.SIZE * oldHeight)
/ oldWidth;
} else {
newWidth = (ImagePanel.SIZE * oldWidth) / oldHeight;
newHeight = ImagePanel.SIZE;
}
if (tmpImage.isSVG()) {
// Create a new image for the scaled preview image
MyImageD bigImage = new MyImageD(
new BufferedImage(oldWidth, oldHeight,
BufferedImage.TYPE_INT_ARGB));
// draw SVG at full size
GGraphics2DD graphics2D = (GGraphics2DD) bigImage
.createGraphics();
graphics2D.drawImage(tmpImage, 0, 0);
// then scale down
setImg(new MyImageD(new BufferedImage(newWidth,
newHeight, BufferedImage.TYPE_INT_ARGB)));
graphics2D = (GGraphics2DD) getImg().createGraphics();
graphics2D.drawImage(bigImage, 0, 0, newWidth,
newHeight);
graphics2D.dispose();
} else {
// Create a new image for the scaled preview image
setImg(new MyImageD(new BufferedImage(newWidth,
newHeight, BufferedImage.TYPE_INT_RGB)));
GGraphics2DD graphics2D = (GGraphics2DD) getImg()
.createGraphics();
graphics2D.drawImage(tmpImage, 0, 0, newWidth,
newHeight);
graphics2D.dispose();
}
} else {
setImg(tmpImage);
}
tmpImage = null;
} else {
setImg(null);
}
repaint();
} catch (IllegalArgumentException iae) {
// This is thrown if you select .ico files
setImg(null);
} catch (Throwable t) {
t.printStackTrace();
Log.debug(t.getClass() + "");
setImg(null);
}
}
MyImageD getImg() {
return img;
}
private void setImg(MyImageD img) {
this.img = img;
}
/**
* The panel at which the real preview image is drawn.
*
* @author Florian Sonner
*/
private class ImagePanel extends JPanel {
private static final long serialVersionUID = 1L;
/**
* The size of the image panel.
*/
public final static int SIZE = 200;
public ImagePanel() {
setPreferredSize(new Dimension(SIZE, SIZE));
setBorder(BorderFactory.createEtchedBorder());
}
/**
* Paint the preview area.
*/
@Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
// fill background
g2.setColor(Color.white);
g2.fillRect(0, 0, getWidth(), getHeight());
GGraphics2DD.setAntialiasing(g2);
// draw preview image if possible
if (getImg() != null) {
// calculate the scaling factor
int width = getImg().getWidth();
int height = getImg().getHeight();
// center image
int x = ((getWidth() - width) / 2);
int y = ((getHeight() - height) / 2);
// draw the image
getImg().drawImage(g2, x, y, width, height);
// g2.drawImage(img, x, y, width, height, null);
}
// draw "no preview" message
else {
String message = app.getLocalization()
.getMenu("PreviewUnavailable");
FontMetrics fm = g2.getFontMetrics();
Rectangle2D bounds = fm.getStringBounds(message, g2);
g2.setColor(Color.darkGray);
g2.drawString(message,
(float) (getWidth() - bounds.getWidth()) / 2,
(float) (getHeight() - bounds.getHeight()) / 2);
}
}
}
}
/*
* TODO: override to make File Chooser non-modal for VirtualKeyboard
* protected JDialog createDialog(Component parent) throws HeadlessException
* { JDialog dialog = super.createDialog(parent); dialog.setModal(false);
* return dialog; }
*/
}