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; } */ }