/******************************************************************************* * Copyright (C) 2011 Angelo Zerr <angelo.zerr@gmail.com>, Pascal Leclercq <pascal.leclercq@gmail.com> * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Angelo ZERR - initial API and implementation * Pascal Leclercq - initial API and implementation *******************************************************************************/ package org.eclipse.nebula.widgets.picture; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Locale; import java.util.ResourceBundle; import org.eclipse.nebula.widgets.picture.forms.FormPictureControl; import org.eclipse.nebula.widgets.picture.internal.IOUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; /** * Picture Control gives you the capability to display an image picture in a SWT * Label and change it with "Modify" Link. This class is abstract must be * implemented to override methodes whicg create SWT {@link Label}, SWT * {@link Composite} and Link according if you use only SWT (see * {@link PictureControl}) or SWT Form Toolkit (see {@link FormPictureControl}. * * @param <T> * the "Modify" Link control used to upload a new image picture. */ public abstract class AbstractPictureControl<T extends Control> extends Composite implements PropertyChangeListener { /** Bundle name constant */ public static final String BUNDLE_NAME = "org.eclipse.nebula.widgets.picture.resources"; //$NON-NLS-1$ /** Resources constants */ private static final String PICTURE_CONTROL_DELETE = "PictureControl.delete"; private static final String PICTURE_CONTROL_MODIFY = "PictureControl.modify"; private static final String PICTURE_CONTROL_FILEDIALOG_TEXT = "PictureControl.fileDialog.text"; public static final String IMAGE_BYTEARRAY_PROPERTY = "imageByteArray"; private static final String[] DEFAULT_EXTENSIONS; static { DEFAULT_EXTENSIONS = ImageFilterExtension.createFilterExtension(true, ImageFilterExtension.values()); } /** Picture label which host the picture image **/ private Label pictureLabel; /** "Modify" image link **/ private T modifyImageLink; /** "Delete" image link **/ private T deleteImageLink; /** Current picture image byte array **/ private byte[] imageByteArray; /** Current resized picture image **/ private Image resizedPictureImage; /** The maximum width of the picture image **/ private Integer maxImageWidth = 96; /** The minimum height of the picture image **/ private Integer maxImageHeight = 96; /** Resources bundle */ protected ResourceBundle resources; /** Default image picture to display when none picture is displayed **/ private Image defaultImage; private String[] extensions; private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport( this); /** The SWT GridData of the picture label **/ private GridData pictureLabelImageGridData; private MenuItem deleteItem; /** * Constructor for {@link AbstractPictureControl} with default SWT styles. * * @param parent * a composite control which will be the parent of the new * instance (cannot be null) */ public AbstractPictureControl(Composite parent) { this(parent, SWT.NONE, SWT.BORDER | SWT.CENTER, SWT.NONE, true); } /** * Constructor for {@link AbstractPictureControl} with given SWT style . * * @param parent * a composite control which will be the parent of the new * instance (cannot be null) * * @param compositeStyle * SWT style of the SWT Composite which host Label+Link controls. * @param labelStyle * SWT style of the Label control. * @param linkStyle * SWT style of the Link control. */ public AbstractPictureControl(Composite parent, int compositeStyle, int labelStyle, int linkStyle) { this(parent, compositeStyle, labelStyle, linkStyle, true); } /** * Constructor for {@link AbstractPictureControl} with given SWT styles. * * @param parent * a composite control which will be the parent of the new * instance (cannot be null) * * @param compositeStyle * SWT style of the SWT Composite which host Label+Link controls. * @param labelStyle * SWT style of the Label control. * @param linkStyle * SWT style of the Link control. * @param createUI * true if UI must be created and false otherwise. */ protected AbstractPictureControl(Composite parent, int compositeStyle, int labelStyle, int linkStyle, boolean createUI) { super(parent, compositeStyle); setLocale(Locale.getDefault()); setFilterExtensions(DEFAULT_EXTENSIONS); if (createUI) { createUI(labelStyle, linkStyle); } } /** * Create the UI picture control composed with Label (for host the image * picture) * * @param labelStyle * the SWT label style. * @param linkStyle * the link style. */ protected Composite createUI(int labelStyle, int linkStyle) { // Layout of the global COmposite GridLayout layout = new GridLayout(); layout.numColumns = 1; layout.marginTop = 0; layout.verticalSpacing = 0; layout.marginWidth = 0; layout.marginHeight = 0; this.setLayout(layout); // Create internal composite Composite parent = createComposite(this, SWT.NONE); layout = new GridLayout(); layout.numColumns = 2; layout.verticalSpacing = 0; layout.marginWidth = 0; parent.setLayout(layout); GridData g = new GridData(); g.verticalAlignment = SWT.TOP; parent.setLayoutData(g); // Create Label to host the image picture. this.pictureLabel = createLabelImage(parent, labelStyle); // Create the link to "Modify" the image this.modifyImageLink = createModifyLink(parent, linkStyle); this.deleteImageLink = createDeleteLink(parent, linkStyle); setDeleteLinkEnabled(false); return parent; } /** * Create the SWT {@link Label} to host the image picture. * * @param parent * a composite control which will be the parent of the new * instance (cannot be null) * @param style * the style of control to construct * @return */ protected Label createLabelImage(Composite parent, int style) { // Create a Label Label label = createLabel(parent, style); // Create SWT GridData. the size is managed with maxImageWidth and // maxImageHeight pictureLabelImageGridData = new GridData(); pictureLabelImageGridData.horizontalAlignment = SWT.CENTER; pictureLabelImageGridData.verticalAlignment = SWT.CENTER; pictureLabelImageGridData.horizontalSpan = 2; label.setLayoutData(pictureLabelImageGridData); setMaxImageWidth(maxImageWidth); setMaxImageHeight(maxImageHeight); // Create a menu with "Delete", "Modify" Item. Menu menu = createMenu(label); if (menu != null) { label.setMenu(menu); } return label; } /** * Create the menu with "Delete", "Modify" Item. * * @param parent * @return */ protected Menu createMenu(Control parent) { Menu menu = new Menu(parent); // "Delete" menu item. deleteItem = new MenuItem(menu, SWT.NONE); deleteItem.setText(resources.getString(PICTURE_CONTROL_DELETE)); deleteItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // Delete the image. AbstractPictureControl.this.handleDeleteImage(); } }); // "Modify" menu item. final MenuItem modifyItem = new MenuItem(menu, SWT.NONE); modifyItem.setText(resources.getString(PICTURE_CONTROL_MODIFY)); modifyItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // Modify the image. AbstractPictureControl.this.handleModifyImage(); } }); return menu; } /** * Create the "Modify" Link to open Explorer File to select a new image. * * @param parent * @param style * @return */ private T createModifyLink(Composite parent, int style) { T modifyImageLink = createLink(parent, style); GridData gridData = new GridData(GridData.CENTER, GridData.CENTER, true, false); modifyImageLink.setLayoutData(gridData); setLinkText(modifyImageLink, resources.getString(PICTURE_CONTROL_MODIFY)); addModifyImageHandler(modifyImageLink); return modifyImageLink; } /** * Create the "Delete" Link to delete the current image. * * @param parent * @param style * @return */ private T createDeleteLink(Composite parent, int style) { T deleteImageLink = createLink(parent, style); GridData gridData = new GridData(GridData.CENTER, GridData.CENTER, true, false); deleteImageLink.setLayoutData(gridData); setLinkText(deleteImageLink, resources.getString(PICTURE_CONTROL_DELETE)); addDeleteImageHandler(deleteImageLink); return deleteImageLink; } /** * Set the text of the "Modify" Link. * * @param text */ public void setModifyImageLinkText(String text) { setLinkText(getModifyImageLink(), text); } /** * Set the text of the "Modify" Link. * * @param text */ public void setDeleteImageLinkText(String text) { setLinkText(getDeleteImageLink(), text); } /** * Delete the current image picture. */ protected void handleDeleteImage() { setImageByteArray(null); } /** * Set the file extensions which the dialog will use to filter the files it * shows to the argument, which may be null. * <p> * The strings are platform specific. For example, on some platforms, an * extension filter string is typically of the form "*.extension", where * "*.*" matches all files. For filters with multiple extensions, use * semicolon as a separator, e.g. "*.jpg;*.png". * </p> * * @param extensions * the file extension filter * * @see #setFilterNames to specify the user-friendly names corresponding to * the extensions */ public void setFilterExtensions(String[] extensions) { this.extensions = extensions; } /** * Open the Explorer File to select a new image. */ protected void handleModifyImage() { FileDialog fd = new FileDialog(this.getShell(), getFileDialogStyle()); configure(fd); String selected = fd.open(); if (selected != null && selected.length() > 0) { File f = new File(selected); try { FileInputStream in = new FileInputStream(f); setImageStream(in); } catch (Throwable e) { handleError(e); } } } /** * Configure the {@link FileDialog} to set the file extension, the text, * etc. This method can be override to custome the configuration. * * @param fd */ protected void configure(FileDialog fd) { fd.setText(resources.getString(PICTURE_CONTROL_FILEDIALOG_TEXT)); if (extensions != null) { fd.setFilterExtensions(extensions); } } /** * Returns the {@link FileDialog}SWT style. This method can be override if * the SWT style should be customized. * * @return */ protected int getFileDialogStyle() { return SWT.SHELL_TRIM | SWT.SINGLE | SWT.APPLICATION_MODAL; } /** * Handle error when file selected cannot be loaded as Image. * * @param e */ protected void handleError(Throwable e) { e.printStackTrace(); } /** * Returns the picture label which hosts the picture image. * * @return */ public Label getPictureLabel() { return pictureLabel; } /** * Returns the "Modify" Link control used to open Explorer files to change * image. * * @return */ public T getModifyImageLink() { return modifyImageLink; } /** * Returns the "Delete" Link control used to open Explorer files to change * image. * * @return */ public T getDeleteImageLink() { return deleteImageLink; } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(propertyName, listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } public void propertyChange(PropertyChangeEvent arg0) { } /** * Set the current {@link InputStream} of the image picture. null is * accepted to delete the image. * * @param stream * @throws IOException */ public void setImageStream(InputStream stream) throws IOException { if (stream == null) { setImageByteArray(null); } else { setImageByteArray(IOUtils.toByteArray(stream)); } } /** * Returns the {@link InputStream} of the image picture and null if none * picture was setted. * * @return */ public InputStream getImageStream() { byte[] imageByteArray = getImageByteArray(); if (imageByteArray == null) { return null; } return new ByteArrayInputStream(imageByteArray); } /** * Set the current byte array of the image picture. null is accepted to * delete the image. * * @param imageByteArray */ public void setImageByteArray(byte[] imageByteArray) { byte[] oldImageByteArray = this.imageByteArray; this.imageByteArray = imageByteArray; // Dispose the old image. disposePictureImage(); if (imageByteArray != null) { // byte array is not null. // Create ImageData ImageData imageData = new ImageData(new ByteArrayInputStream( imageByteArray)); // Resize image if needed. final ImageData resizedImageData = getResizedImageData(imageData); this.resizedPictureImage = new Image(super.getDisplay(), resizedImageData); // Set the new image pictureLabel.setImage(resizedPictureImage); setDeleteLinkEnabled(true); } else { // byte array is null // default image was defined, set as the image. pictureLabel.setImage(defaultImage); setDeleteLinkEnabled(false); } propertyChangeSupport.firePropertyChange(IMAGE_BYTEARRAY_PROPERTY, oldImageByteArray, imageByteArray); } /** * Returns the resized {@link ImageData}. This method can be override if * scale logic doen't please you. * * @param imageData * @return */ protected ImageData getResizedImageData(ImageData imageData) { int height = imageData.height; int width = imageData.width; if (height <= maxImageHeight) { return imageData; } int newHeight = maxImageHeight; float w = (float) width; float h = (float) height; float nw = (w / h) * maxImageHeight; int newWidth = (int) nw; return imageData.scaledTo(newWidth, newHeight); } /** * Returns the byte array of the image picture and null if none picture was * setted. * * @return */ public byte[] getImageByteArray() { return imageByteArray; } /** * Sets a new locale to use for picture controle. Locale will choose the * well resources bundle. * * @param locale * new locale (must not be null) */ public void setLocale(Locale locale) { checkWidget(); if (locale == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); // Loads the resources resources = ResourceBundle.getBundle(BUNDLE_NAME, locale); } /** * Set the maximum height of the image. * * @param maxImageHeight */ public void setMaxImageHeight(Integer maxImageHeight) { this.maxImageHeight = maxImageHeight; if (maxImageHeight != null) { pictureLabelImageGridData.heightHint = maxImageHeight; } else { pictureLabelImageGridData.heightHint = SWT.DEFAULT; } } /** * Returns the maximum height of the image. * * @param maxImageHeight * * @return */ public Integer getMaxImageHeight() { return maxImageHeight; } /** * Set the maximum width of the image. * * @param maxImageWidth */ public void setMaxImageWidth(Integer maxImageWidth) { this.maxImageWidth = maxImageWidth; if (maxImageWidth != null) { pictureLabelImageGridData.widthHint = maxImageWidth; } else { pictureLabelImageGridData.widthHint = SWT.DEFAULT; } } /** * Returns the maximum width of the image. * * @param maxImageWidth * * @return */ public Integer getMaxImageWidth() { return maxImageWidth; } /** * Set the default image for the picture. The default image doesn't store * the input stream of the image in this control. It is used just to display * an "empty" picture and set the maximum/minimum width of the picture * Label. * * @param defaultImage */ public void setDefaultImage(Image defaultImage) { this.defaultImage = defaultImage; ImageData imageData = defaultImage.getImageData(); setMaxImageHeight(imageData.height); setMaxImageWidth(imageData.width); setImageByteArray(null); } private void setDeleteLinkEnabled(boolean enabled) { deleteImageLink.setEnabled(enabled); if (deleteItem != null) { deleteItem.setEnabled(enabled); } } /** * Dispose the current image if needed.s */ private void disposePictureImage() { if (this.resizedPictureImage != null && !resizedPictureImage.isDisposed()) { this.resizedPictureImage.dispose(); } } @Override public void dispose() { disposePictureImage(); super.dispose(); } /** * Create a SWT {@link Label}. * * @param parent * a composite control which will be the parent of the new * instance (cannot be null) * @param style * the style of control to construct * @return */ protected abstract Label createLabel(Composite parent, int style); /** * Create a SWT control for the "Modify" Link. * * @param parent * a composite control which will be the parent of the new * instance (cannot be null) * @param style * the style of control to construct * @return */ protected abstract T createLink(Composite parent, int style); /** * Create a SWT {@link Composite}. * * @param parent * a composite control which will be the parent of the new * instance (cannot be null) * @param style * the style of control to construct * @return */ protected abstract Composite createComposite(Composite parent, int style); /** * Set the text of a Link control. * * @param modifyImageLink * @param text */ protected abstract void setLinkText(T link, String text); /** * Add the handler to open Explorer files to the Link control. * * @param modifyImageLink */ protected abstract void addModifyImageHandler(T modifyImageLink); /** * Add the handler to delete the image to the Link control. * * @param modifyImageLink */ protected abstract void addDeleteImageHandler(T deleteImageLink); }