package org.rr.commons.swing.dialogs;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingWorker;
import javax.swing.border.EmptyBorder;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import org.rr.commons.log.LoggerFactory;
import org.rr.commons.mufs.IResourceHandler;
import org.rr.commons.mufs.ResourceHandlerFactory;
import org.rr.commons.net.imagefetcher.IImageFetcher;
import org.rr.commons.net.imagefetcher.IImageFetcherEntry;
import org.rr.commons.net.imagefetcher.IImageFetcherFactory;
import org.rr.commons.net.imagefetcher.ImageWebSearchFetcherFactory;
import org.rr.commons.swing.SwingUtils;
import org.rr.commons.swing.components.JRScrollPane;
import org.rr.commons.swing.components.container.ShadowPanel;
import org.rr.commons.swing.layout.EqualsLayout;
import org.rr.commons.utils.ThreadUtils;
import org.rr.pm.image.IImageProvider;
import org.rr.pm.image.ImageProviderFactory;
import org.rr.pm.image.ImageUtils;
public class JImageDownloadDialog extends JDialog {
private static int previousImageFetcherComboboxEntry = 0;
private IResourceHandler selectedImage;
private JTextField searchTextField;
private JComboBox searchProviderComboBox;
private JRScrollPane scrollPane;
private JButton okButton;
private static Dimension cellSize = new Dimension(100, 200);
private IImageFetcherFactory factory;
/**
* Number of images to be loaded into the dialog.
*/
private int resultCount = 20;
private Color selectedFgColor;
private Color selectedBgColor;
private Color bgColor;
private Color fgColor;
private final ActionListener cancelAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
closeDialog(null);
}
};
private final ActionListener okAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
storeSelectionAndCloseDialog();
}
};
public JImageDownloadDialog(JFrame owner, IImageFetcherFactory factory) {
super(owner);
this.factory = factory;
init(owner, factory);
}
public JImageDownloadDialog(IImageFetcherFactory factory) {
super();
this.factory = factory;
init(null, factory);
}
protected void init(final Frame owner, final IImageFetcherFactory factory) {
if(factory.searchTermSupport()) {
this.setSize(800, 375);
} else {
this.setSize(800, 330);
}
if(owner != null) {
//center over the owner frame
this.setLocation(owner.getBounds().x + owner.getBounds().width/2 - this.getSize().width/2, owner.getBounds().y + 50);
}
this.setTitle(Bundle.getString("ImageDownloadDialog.title"));
this.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
this.setGlassPane(new ShadowPanel());
getGlassPane().setVisible(false);
((JComponent)getContentPane()).registerKeyboardAction(cancelAction, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
//workaround for a swing bug. The first time, the editor is used, the
//ui color instance draws the wrong color but have the right rgb values.
Color color;
color = SwingUtils.getSelectionForegroundColor();
selectedFgColor = new Color(color.getRed(), color.getGreen(), color.getBlue());
color = SwingUtils.getSelectionBackgroundColor();
selectedBgColor = new Color(color.getRed(), color.getGreen(), color.getBlue());
color = SwingUtils.getBackgroundColor();
bgColor = new Color(color.getRed(), color.getGreen(), color.getBlue());
color = SwingUtils.getForegroundColor();
fgColor = new Color(color.getRed(), color.getGreen(), color.getBlue());
GridBagLayout gridBagLayout = new GridBagLayout();
gridBagLayout.columnWidths = new int[]{0, 0};
gridBagLayout.rowHeights = new int[]{0, 0};
gridBagLayout.columnWeights = new double[]{1.0, Double.MIN_VALUE};
gridBagLayout.rowWeights = new double[]{1.0, Double.MIN_VALUE};
getContentPane().setLayout(gridBagLayout);
JPanel borderPanel = new JPanel();
borderPanel.setBorder(new EmptyBorder(3, 3, 3, 3));
GridBagConstraints gbc_borderPanel = new GridBagConstraints();
gbc_borderPanel.fill = GridBagConstraints.BOTH;
gbc_borderPanel.gridx = 0;
gbc_borderPanel.gridy = 0;
getContentPane().add(borderPanel, gbc_borderPanel);
GridBagLayout gbl_borderPanel = new GridBagLayout();
gbl_borderPanel.columnWidths = new int[]{0, 340, 0, 0};
gbl_borderPanel.rowHeights = new int[]{0, 0, 0, 0};
gbl_borderPanel.columnWeights = new double[]{1.0, 1.0, 0.0, Double.MIN_VALUE};
gbl_borderPanel.rowWeights = new double[]{0.0, 1.0, 0.0, Double.MIN_VALUE};
borderPanel.setLayout(gbl_borderPanel);
if(factory.searchTermSupport()) {
searchProviderComboBox = new JComboBox();
GridBagConstraints gbc_searchProviderComboBox = new GridBagConstraints();
gbc_searchProviderComboBox.insets = new Insets(0, 0, 5, 5);
gbc_searchProviderComboBox.fill = GridBagConstraints.HORIZONTAL;
gbc_searchProviderComboBox.gridx = 0;
gbc_searchProviderComboBox.gridy = 0;
borderPanel.add(searchProviderComboBox, gbc_searchProviderComboBox);
searchProviderComboBox.setModel(new DefaultComboBoxModel(factory.getFetcherNames().toArray()));
searchProviderComboBox.setSelectedIndex(previousImageFetcherComboboxEntry);
searchTextField = new JTextField();
GridBagConstraints gbc_searchTextField = new GridBagConstraints();
gbc_searchTextField.fill = GridBagConstraints.BOTH;
gbc_searchTextField.insets = new Insets(0, 0, 5, 5);
gbc_searchTextField.gridx = 1;
gbc_searchTextField.gridy = 0;
borderPanel.add(searchTextField, gbc_searchTextField);
searchTextField.setColumns(10);
searchTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER) {
startSearch();
}
}
});
JButton searchButton = new JButton(new SearchAction());
searchButton.setMargin(new Insets(0, 8, 0, 8));
GridBagConstraints gbc_searchButton = new GridBagConstraints();
gbc_searchButton.fill = GridBagConstraints.VERTICAL;
gbc_searchButton.insets = new Insets(0, 0, 5, 0);
gbc_searchButton.gridx = 2;
gbc_searchButton.gridy = 0;
borderPanel.add(searchButton, gbc_searchButton);
}
scrollPane = new JRScrollPane();
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
GridBagConstraints gbc_scrollPane = new GridBagConstraints();
gbc_scrollPane.gridwidth = 3;
gbc_scrollPane.insets = new Insets(0, 0, 5, 0);
gbc_scrollPane.fill = GridBagConstraints.BOTH;
gbc_scrollPane.gridx = 0;
gbc_scrollPane.gridy = 1;
borderPanel.add(scrollPane, gbc_scrollPane);
JPanel panel = new JPanel();
panel.setLayout(new EqualsLayout(3));
GridBagConstraints gbc_panel = new GridBagConstraints();
gbc_panel.gridwidth = 3;
gbc_panel.fill = GridBagConstraints.HORIZONTAL;
gbc_panel.gridx = 0;
gbc_panel.gridy = 2;
borderPanel.add(panel, gbc_panel);
JButton abortButton = new JButton(Bundle.getString("ImageDownloadDialog.Action.Cancel"));
abortButton.setMargin(new Insets(2, 8, 2, 8));
panel.add(abortButton);
abortButton.addActionListener(cancelAction);
okButton = new JButton(Bundle.getString("ImageDownloadDialog.Action.OK"));
panel.add(okButton);
okButton.addActionListener(okAction);
}
/**
* Stores the selection for providing it for the {@link #getSelectedImage()} method
* and closes this {@link JImageDownloadDialog} instance.
* @see #closeDialog(IResourceHandler)
*/
private void storeSelectionAndCloseDialog() {
SearchResultPanel view = (SearchResultPanel) scrollPane.getViewport().getView();
if(view != null) {
final int selectedColumn = view.getSelectedColumn();
final IImageFetcherEntry imageFetcher = (IImageFetcherEntry) view.getModel().getValueAt(0, selectedColumn);
if(imageFetcher != null) {
try {
byte[] imageBytes = imageFetcher.getImageBytes();
String path = imageFetcher.getImageURL().toString();
IResourceHandler image = ResourceHandlerFactory.getVirtualResourceHandler(path, imageBytes);
closeDialog(image);
} catch (Exception e1) {
LoggerFactory.getLogger(this).log(Level.WARNING, "Could not fetch image from " + imageFetcher.getImageURL());
}
}
}
}
/**
* Closes and disposes this {@link JImageDownloadDialog} instance.
* @param selectedImage The image to be set as result.
*/
private void closeDialog(IResourceHandler selectedImage) {
this.selectedImage = selectedImage;
setVisible(false);
dispose();
}
public void setVisible(boolean b) {
if(b && !factory.searchTermSupport()) {
startSearch();
}
super.setVisible(b);
}
private void startSearch() {
getGlassPane().setVisible(true);
new SwingWorker<SearchResultPanel, SearchResultPanel>() {
@Override
protected SearchResultPanel doInBackground() throws Exception {
String searchText = searchTextField != null ? searchTextField.getText() : null;
String selectedItem = searchProviderComboBox != null ? searchProviderComboBox.getSelectedItem().toString() : null;
previousImageFetcherComboboxEntry = searchProviderComboBox != null ? searchProviderComboBox.getSelectedIndex() : 0;
SearchResultPanel searchResultPanel = new SearchResultPanel(searchText, selectedItem);
return searchResultPanel;
}
@Override
protected void done() {
try {
SearchResultPanel searchResultPanel = get();
scrollPane.setViewportView(searchResultPanel);
getGlassPane().setVisible(false);
} catch (Exception e) {
LoggerFactory.getLogger().log(Level.WARNING, "Error while setting search reuslts.", e);
}
}
}.execute();
}
private class SearchAction extends AbstractAction {
private SearchAction() {
final URL resource = JImageDownloadDialog.class.getResource("resources/play_16.gif");
putValue(Action.SMALL_ICON, new ImageIcon(resource));
}
@Override
public void actionPerformed(ActionEvent e) {
startSearch();
}
}
private class SearchResultPanel extends JTable {
private Map<URL, ImageIcon> imageCache = Collections.synchronizedMap(new HashMap<URL, ImageIcon>(30));
private TableCellRenderer renderer = new SearchResultTableRenderer();
private SearchResultPanel(String searchTerm, String searchProviderName) {
this.setLayout(new FlowLayout(FlowLayout.LEFT));
this.setRowHeight(cellSize.height);
this.setTableHeader(null);
this.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
this.setShowGrid(false);
this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
this.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
storeSelectionAndCloseDialog();
}
}
});
this.setModel(new SearchResultTableModel(searchTerm, searchProviderName));
this.setDefaultRenderer(Object.class, renderer);
int columnCount = this.getModel().getColumnCount();
for (int i = 0; i < columnCount; i++) {
TableColumn column = this.getColumnModel().getColumn(i);
column.setPreferredWidth(cellSize.width);
}
}
/**
* Creates an {@link ImageIcon} from the given {@link IImageFetcherEntry}.
* @param entry The {@link IImageFetcherEntry} containing the url for the thumbnail image.
* @param thumbnailURL
* @return
* @throws IOException
*/
private ImageIcon createThumbnail(IImageFetcherEntry entry) throws IOException {
final URL thumbnailURL = entry.getThumbnailURL();
ImageIcon imageIcon = imageCache.get(thumbnailURL);
if(imageIcon == null) {
byte[] thumbnailImageBytes = entry.getThumbnailImageBytes();
if(thumbnailImageBytes != null && thumbnailImageBytes.length > 0) {
IImageProvider imageProvider = ImageProviderFactory.getImageProvider(ResourceHandlerFactory.getResourceHandler(new ByteArrayInputStream(thumbnailImageBytes)));
BufferedImage image = imageProvider.getImage();
if(image != null) {
BufferedImage scaleToMatch = ImageUtils.scaleToMatch(image, cellSize, true);
if(scaleToMatch != null) {
imageIcon = new ImageIcon(scaleToMatch);
imageCache.put(thumbnailURL, imageIcon);
}
}
}
}
return imageIcon;
}
private class SearchResultTableRenderer extends JPanel implements TableCellRenderer {
private JLabel sizeLabel;
private JLabel imageLabel;
private SearchResultTableRenderer() {
//init
this.setLayout(new BorderLayout());
this.setBorder(new EmptyBorder(0, 3, 0, 3));
sizeLabel = new JLabel();
sizeLabel.setOpaque(false);
sizeLabel.setHorizontalAlignment(SwingConstants.CENTER);
this.add(sizeLabel, BorderLayout.SOUTH);
imageLabel = new JLabel();
imageLabel.setOpaque(false);
imageLabel.setHorizontalAlignment(SwingConstants.CENTER);
this.add(imageLabel, BorderLayout.CENTER);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if(table.getSelectedColumn() == column) {
setBackground(selectedBgColor);
sizeLabel.setForeground(selectedFgColor);
imageLabel.setForeground(selectedFgColor);
} else {
setBackground(bgColor);
sizeLabel.setForeground(fgColor);
imageLabel.setForeground(fgColor);
}
int imageHeight = ((IImageFetcherEntry)value).getImageHeight();
int imageWidth = ((IImageFetcherEntry)value).getImageWidth();
if(imageHeight >= 0 && imageWidth >= 0) {
sizeLabel.setText(imageWidth + "x" + imageHeight);
} else {
try {
String path = ((IImageFetcherEntry)value).getImageURL().getPath();
sizeLabel.setText(new File(path).getName());
} catch(Exception e) {
//no label in case something goes wrong
}
}
try {
ImageIcon imageIcon = createThumbnail((IImageFetcherEntry) value);
if(table.getSelectedColumn() == column) {
BufferedImage invertedThumbnail = ImageUtils.invertImage((BufferedImage) imageIcon.getImage());
imageIcon = new ImageIcon(invertedThumbnail);
}
imageLabel.setIcon(imageIcon);
} catch (IOException e) {
LoggerFactory.getLogger(this).log(Level.WARNING, "Images could not be retrieved.", e);
}
return this;
}
}
/**
* Model loading and providing the images from the selected search provider.
*/
private class SearchResultTableModel extends AbstractTableModel {
final List<IImageFetcherEntry> thumbnailEntries = Collections.synchronizedList(new ArrayList<IImageFetcherEntry>());
private SearchResultTableModel(String searchTerm, String searchProviderName) {
try { //init
final IImageFetcher imageFetcher = factory.getImageFetcher(searchProviderName, searchTerm);
final List<IImageFetcherEntry> imageFetcherEntries = this.createImageFetcherEntries(imageFetcher);
thumbnailEntries.addAll(imageFetcherEntries);
ThreadUtils.RunnableImpl<IImageFetcherEntry, Void> each = new ThreadUtils.RunnableImpl<IImageFetcherEntry, Void>() {
@Override
public Void run(IImageFetcherEntry entry) {
try {
createThumbnail(entry);
} catch (IOException e) {
LoggerFactory.getLogger(this).log(Level.INFO, "Failed fetching " + entry.getImageURL(), e);
}
return null;
}
};
ThreadUtils.loopAndWait(imageFetcherEntries, each, 8);
} catch (Exception e) {
LoggerFactory.getLogger(this).log(Level.WARNING, "Images could not be retrieved.", e);
}
}
private List<IImageFetcherEntry> createImageFetcherEntries(final IImageFetcher imageFetcher) {
final int max = getMaxDisplayedThumbnails();
final Iterator<IImageFetcherEntry> entriesIterator = imageFetcher.getEntriesIterator();
List<IImageFetcherEntry> imageFetcherEntries = new ArrayList<>(max);
while(true) {
try {
for(int i = 0; i < max; i++) {
IImageFetcherEntry entry = entriesIterator.next();
imageFetcherEntries.add(entry);
}
} catch(ArrayIndexOutOfBoundsException e) {
//no more entries
} catch(NoSuchElementException e) {
//no more entries
}
break;
}
return imageFetcherEntries;
}
@Override
public int getRowCount() {
return 1;
}
@Override
public int getColumnCount() {
return thumbnailEntries.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return thumbnailEntries.get(columnIndex);
}
}
}
public String getSearchPhrase() {
return this.searchTextField.getText();
}
public void setSearchPhrase(String searchPhrase) {
this.searchTextField.setText(searchPhrase);
}
public IResourceHandler getSelectedImage() {
return selectedImage;
}
public int getMaxDisplayedThumbnails() {
return resultCount;
}
public void setResultCount(int resultCount) {
this.resultCount = resultCount;
}
public static void main(String[] args) {
JImageDownloadDialog imageDownloadDialog = new JImageDownloadDialog(ImageWebSearchFetcherFactory.getInstance());
imageDownloadDialog.setVisible(true);
System.out.println(imageDownloadDialog.getSelectedImage());
System.exit(0);
}
}