/* * ------------------------------------------------------------------------ * * Copyright (C) 2003 - 2013 * University of Konstanz, Germany and * KNIME GmbH, Konstanz, Germany * Website: http://www.knime.org; Email: contact@knime.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, Version 3, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <http://www.gnu.org/licenses>. * * Additional permission under GNU GPL version 3 section 7: * * KNIME interoperates with ECLIPSE solely via ECLIPSE's plug-in APIs. * Hence, KNIME and ECLIPSE are both independent programs and are not * derived from each other. Should, however, the interpretation of the * GNU GPL Version 3 ("License") under any applicable laws result in * KNIME and ECLIPSE being a combined program, KNIME GMBH herewith grants * you the additional permission to use and propagate KNIME together with * ECLIPSE with only the license terms in place for ECLIPSE applying to * ECLIPSE and the GNU GPL Version 3 applying for KNIME, provided the * license terms of ECLIPSE themselves allow for the respective use and * propagation of ECLIPSE together with KNIME. * * Additional permission relating to nodes for KNIME that extend the Node * Extension (and in particular that are based on subclasses of NodeModel, * NodeDialog, and NodeView) and that only interoperate with KNIME through * standard APIs ("Nodes"): * Nodes are deemed to be separate and independent programs and to not be * covered works. Notwithstanding anything to the contrary in the * License, the License does not apply to Nodes, you are not required to * license Nodes under the License, and you are granted a license to * prepare and propagate Nodes, in each case even if such Nodes are * propagated with or for interoperation with KNIME. The owner of a Node * may freely choose the license terms applicable to such Node, including * when such Node is propagated with or for interoperation with KNIME. * --------------------------------------------------------------------- * * */ package org.knime.knip.io.nodes.imgreader; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dialog.ModalityType; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.StringTokenizer; import java.util.Vector; import java.util.prefs.Preferences; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.ListModel; import javax.swing.LookAndFeel; import javax.swing.UIManager; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileView; import org.knime.base.util.WildcardMatcher; import org.knime.knip.core.ui.event.EventService; import org.knime.knip.core.ui.imgviewer.events.FileChooserSelectedFilesChgEvent; import org.knime.knip.io.node.dialog.ImagePreviewPanel; /** * TODO Auto-generated * * @author <a href="mailto:dietzc85@googlemail.com">Christian Dietz</a> * @author <a href="mailto:horn_martin@gmx.de">Martin Horn</a> * @author <a href="mailto:michael.zinsmaier@googlemail.com">Michael * Zinsmaier</a> */ public class FileChooserPanel extends JPanel { class ApplyFileView extends FileView { Icon newicon = new ImageIcon(getClass().getResource("button_ok.png")); @Override public Icon getIcon(final File file) { if (file.isDirectory()) { return null; } Icon icon = null; if (m_selectedFileListModel.contains(file)) { icon = newicon; } return icon; } } @SuppressWarnings("rawtypes") private class FileListModel implements ListModel, Comparator<File> { private final ArrayList<ListDataListener> listener = new ArrayList<ListDataListener>(); private final Vector<File> m_files = new Vector<File>(); public void addFiles(final File[] files, final FileFilter fileFilter) { final LinkedList<File> directoryQueue = new LinkedList<File>(); // init insertTopFiles(directoryQueue, fileFilter, files); // loop while (!directoryQueue.isEmpty()) { final File dir = directoryQueue.poll(); insertTopFiles(directoryQueue, fileFilter, dir.listFiles()); } notifyListDataListener(); } /** * {@inheritDoc} */ @Override public void addListDataListener(final ListDataListener arg0) { listener.add(arg0); } /** * {@inheritDoc} */ @Override public int compare(final File o1, final File o2) { return o1.getName().compareToIgnoreCase(o2.getName()); } public Boolean contains(final File f) { return m_files.contains(f); } public String getAbsolutePathAt(final int idx) { return m_files.get(idx).getAbsolutePath(); } /** * {@inheritDoc} */ @Override public Object getElementAt(final int arg0) { return m_files.get(arg0).getName(); } /** * {@inheritDoc} */ @Override public int getSize() { return m_files.size(); } private void insertTopFiles(final LinkedList<File> directoryQueue, final FileFilter fileFilter, final File[] files) { for (final File f : files) { if (f.isDirectory()) { directoryQueue.add(f); } else { if (fileFilter.accept(f) && !m_files.contains(f)) { m_files.add(f); } } } } private void notifyListDataListener() { for (final ListDataListener l : listener) { l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, getSize())); } } public void remove(final int[] indices) { for (int i = indices.length - 1; i >= 0; i--) { m_files.remove(indices[i]); } notifyListDataListener(); } public void removeAll() { m_files.clear(); notifyListDataListener(); } /** * {@inheritDoc} */ @Override public void removeListDataListener(final ListDataListener arg0) { listener.remove(arg0); } public void removeMenu(final File[] files) { for (final File f : files) { m_files.remove(f); } } } // a class to show the image preview with only one click (File // Selection) class ImagePreviewListener implements PropertyChangeListener { ImagePreviewPanel m_imagePreviewPanel; public ImagePreviewListener(final ImagePreviewPanel imagePreviewPanel) { m_imagePreviewPanel = imagePreviewPanel; } @Override public void propertyChange(final PropertyChangeEvent e) { if (e.getNewValue() instanceof File) { final String propertyName = e.getPropertyName(); final File selection = (File) e.getNewValue(); if (propertyName.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) { m_imagePreviewPanel.setImage(selection.toString()); } } } } class MacHackedFileChooserPanel extends JFileChooser { /** * */ private static final long serialVersionUID = 1L; @Override protected void firePropertyChange(final String propertyName, final Object oldValue, final Object newValue) { try { super.firePropertyChange(propertyName, oldValue, newValue); } catch (final Exception e) { // This is a hack to avoid // stupid mac behaviour } } } static final long serialVersionUID = 1; /** * Shows a modal dialog to select some files * * @param file_list * default files in the selected file list * @return the file list returns <code>null</code> if the dialog was closed * with cancel, or ESC or X */ public static String[] showFileChooserDialog(final FileFilter filter, final String[] file_list) { final JDialog dlg = new JDialog(); dlg.setAlwaysOnTop(true); dlg.setModalityType(ModalityType.APPLICATION_MODAL); final int[] cancelFlag = new int[] { 0 }; final FileChooserPanel panel = new FileChooserPanel(filter); if (file_list != null) { panel.update(file_list); } final JButton ok = new JButton("OK"); ok.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { cancelFlag[0] = -1; dlg.dispose(); } }); final JButton cancel = new JButton("Cancel"); cancel.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { dlg.dispose(); } }); final JPanel buttons = new JPanel(); buttons.add(ok); buttons.add(cancel); dlg.getContentPane().setLayout(new BorderLayout()); dlg.getContentPane().add(panel, BorderLayout.CENTER); dlg.getContentPane().add(buttons, BorderLayout.SOUTH); dlg.pack(); dlg.setVisible(true); if (cancelFlag[0] == -1) { return panel.getSelectedFiles(); } return null; } private final File m_defDir; private final FileFilter m_fileFilter; private final Preferences m_filePref = Preferences.userNodeForPackage(getClass()); private final JButton m_addAllButton; // buttons to selected/remove files private final JButton m_addButton; private EventService m_eventService; private final MacHackedFileChooserPanel m_fileChooser; private final ImagePreviewPanel m_imagePreviewPanel; private List<ChangeListener> m_listeners; private final ImagePreviewListener m_previewListener; private final JButton m_remAllButton; // /** // * Creates an new file chooser panel with no files filtered. // */ // public FileChooserPanel2() { // this(null); // } private final JButton m_remButton; /* * action for the add button */ // list and its model keeping the selected files @SuppressWarnings("rawtypes") private final JList m_selectedFileList; /* * * action add all button */ private final TitledBorder m_selectedFileListBorder; private final FileListModel m_selectedFileListModel; private final JPopupMenu popup = new JPopupMenu(); /** * Creates a new file chooser panel * * @param fileFilter * available file name extension filters */ @SuppressWarnings("unchecked") public FileChooserPanel(final FileFilter fileFilter) { final String prefDir = m_filePref.get("Path", "null"); this.m_fileFilter = fileFilter; m_defDir = new File(prefDir); // System.out.println(defDir.toString()); // create instances // mac hack to get the file name text field in the jfilechooser dialog. m_fileChooser = getFileChooserWithLookAndFeel(); m_addButton = new JButton("Add Selected", new ImageIcon(getClass().getResource("button_ok.png"))); m_addAllButton = new JButton("Add all visible files", new ImageIcon(getClass().getResource("edit_add.png"))); m_remButton = new JButton("Remove selected", new ImageIcon(getClass().getResource("editdelete.png"))); m_remAllButton = new JButton("Remove all Files", new ImageIcon(getClass().getResource("edit_remove.png"))); m_selectedFileList = new JList<>(); m_selectedFileListModel = new FileListModel(); m_selectedFileListBorder = BorderFactory.createTitledBorder("Selected files"); final JScrollPane jspSelFileList = new JScrollPane(m_selectedFileList); m_selectedFileList.setModel(m_selectedFileListModel); if (m_selectedFileListModel.getSize() > 0) { m_fileChooser.setCurrentDirectory(new File(m_selectedFileListModel.getAbsolutePathAt(0))); } // arrange the components final JPanel left = new JPanel(); left.setBorder(BorderFactory.createTitledBorder("File browser")); final JPanel right = new JPanel(); right.setBorder(m_selectedFileListBorder); final JPanel center = new JPanel(); center.setBorder(BorderFactory.createTitledBorder("Selection")); left.setLayout(new BorderLayout()); right.setLayout(new BorderLayout()); center.setLayout(new BoxLayout(center, BoxLayout.Y_AXIS)); final JPanel buttonPane = new JPanel(); buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); buttonPane.add(Box.createVerticalStrut(20)); final JPanel delButtonPan = new JPanel(); delButtonPan.setLayout(new BoxLayout(delButtonPan, BoxLayout.X_AXIS)); delButtonPan.add(Box.createVerticalStrut(20)); m_addButton.setMaximumSize(new Dimension(150, 25)); m_addButton.setAlignmentX(Component.CENTER_ALIGNMENT); buttonPane.add(m_addButton); buttonPane.add(Box.createVerticalStrut(20)); m_addAllButton.setMaximumSize(new Dimension(150, 25)); m_addAllButton.setAlignmentX(Component.CENTER_ALIGNMENT); buttonPane.add(m_addAllButton); buttonPane.add(Box.createVerticalStrut(20)); m_remButton.setMaximumSize(new Dimension(150, 25)); m_remButton.setAlignmentX(Component.CENTER_ALIGNMENT); delButtonPan.add(m_remButton); delButtonPan.add(Box.createVerticalStrut(20)); m_remAllButton.setMaximumSize(new Dimension(150, 25)); m_remAllButton.setAlignmentX(Component.CENTER_ALIGNMENT); delButtonPan.add(m_remAllButton); delButtonPan.add(Box.createVerticalStrut(20)); m_imagePreviewPanel = new ImagePreviewPanel(); final ApplyFileView view = new ApplyFileView(); m_fileChooser.setFileView(view); // buttonPan.add(m_imagePreviewPanel); // buttonPan.add(Box.createGlue()); m_fileChooser.setPreferredSize(new Dimension(300, 100)); final JPanel browsePane = new JPanel(); browsePane.setLayout(new BoxLayout(browsePane, BoxLayout.Y_AXIS)); browsePane.add(m_fileChooser); if (fileFilter != null) { // m_fileChooser.setFileFilter(fileFilter); m_fileChooser.setFileFilter(fileFilter); } final JTabbedPane rightTab = new JTabbedPane(); m_fileChooser.setMultiSelectionEnabled(true); m_fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); m_fileChooser.setControlButtonsAreShown(false); m_fileChooser.setPreferredSize(new Dimension(450, 340)); // center.add(buttonPan); // rightTab.setPreferredSize(new Dimension(400, 300)); // browsePane.setPreferredSize(new Dimension(600, 500)); enterHack(m_fileChooser.getComponents()); right.add(rightTab); left.add(browsePane); browsePane.add(buttonPane); final JPanel selectedPane = new JPanel(); selectedPane.setLayout(new BoxLayout(selectedPane, BoxLayout.Y_AXIS)); selectedPane.add(jspSelFileList); selectedPane.add(delButtonPan); // browsePane.add(m_addAllButton); rightTab.add("Selected Files", selectedPane); rightTab.add("Preview/Meta-Data", m_imagePreviewPanel); setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); add(left); // add(center); add(right); m_fileChooser.setComponentPopupMenu(popup); final JMenuItem add = new JMenuItem("Add Selected File", new ImageIcon(getClass().getResource("button_ok.png"))); add.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { onAdd(); fireSelectionChangedEvent(); } }); final JMenuItem addAll = new JMenuItem("Add all Visible Files", new ImageIcon(getClass().getResource("edit_add.png"))); addAll.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { onAddAllTopLevelFiles(); fireSelectionChangedEvent(); } }); final JMenuItem clearSelection = new JMenuItem("Remove All", new ImageIcon(getClass().getResource("edit_remove.png"))); clearSelection.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { m_selectedFileListModel.removeAll(); fireSelectionChangedEvent(); } }); final JMenuItem remove = new JMenuItem("Remove Selected", new ImageIcon(getClass().getResource("editdelete.png"))); remove.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { m_selectedFileListModel.removeMenu(m_fileChooser.getSelectedFiles()); fireSelectionChangedEvent(); } }); final JSeparator sep = new JSeparator(); popup.add(sep); popup.add(add); popup.add(addAll); popup.add(sep); popup.add(remove); popup.add(clearSelection); // Show preview and metadata from the selected file m_previewListener = new ImagePreviewListener(m_imagePreviewPanel); m_fileChooser.addPropertyChangeListener(m_previewListener); m_addButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { onAdd(); } }); m_addAllButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { onAddAllTopLevelFiles(); } }); m_remButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { onRemove(); } }); m_remAllButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { onRemoveAll(); } }); m_fileChooser.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { onAdd(); } }); m_selectedFileList.addMouseListener(new MouseListener() { @Override public void mouseClicked(final MouseEvent arg0) { // Special shortcut to go to directory if (arg0.isControlDown()) { if (m_selectedFileList.getSelectedIndices().length == 0) { return; } final File dir = new File( m_selectedFileListModel.getAbsolutePathAt(m_selectedFileList.getSelectedIndices()[0])); m_fileChooser.setCurrentDirectory(dir); m_selectedFileList.clearSelection(); } else if (arg0.getClickCount() == 2) { onRemove(); } } @Override public void mouseEntered(final MouseEvent arg0) { // Nothing to do here } @Override public void mouseExited(final MouseEvent arg0) { // Nothing to do here } @Override public void mousePressed(final MouseEvent arg0) { // Nothing to do here } @Override public void mouseReleased(final MouseEvent arg0) { // Nothing to do here } }); m_fileChooser.setCurrentDirectory(m_defDir); // m_fileChooser.setVisible(false); // m_fileChooser.setVisible(true); } public void enterHack(final Component[] comp) { for (int x = 0; x < comp.length; x++) { if (comp[x] instanceof JPanel) { enterHack(((JPanel) comp[x]).getComponents()); } else if (comp[x] instanceof JTextField) { final JTextField fl = ((JTextField) comp[x]); ((JTextField) comp[x]).addKeyListener(new KeyListener() { @Override public void keyPressed(final KeyEvent event) { if (event.getKeyCode() == KeyEvent.VK_ENTER) { enterOnTextField(event, fl.getText()); } } @Override public void keyReleased(final KeyEvent arg0) { } @Override public void keyTyped(final KeyEvent arg0) { } }); return; } } } private void enterOnTextField(final KeyEvent event, final String text) { if (text.isEmpty()) { m_fileChooser.setFileFilter(m_fileChooser.getAcceptAllFileFilter()); return; } final File[] dirFiles = m_fileChooser.getCurrentDirectory().listFiles(); final ArrayList<String> textFiles = new ArrayList<String>(); final ArrayList<File> selectedFiles = new ArrayList<File>(); // split at " and ' ' most probably a list of files final StringTokenizer tk = new StringTokenizer(text, "\""); while (tk.hasMoreTokens()) { String next = tk.nextToken(); next = next.trim(); if (next.length() > 0) { textFiles.add(next); } } // search boolean foundAllFiles = true; // maybe optimize this for (final String candidate : textFiles) { boolean match = false; for (final File file : dirFiles) { if (file.getName().equals(candidate)) { match = true; selectedFiles.add(file); break; } } if (!match) { foundAllFiles = false; break; } } if (foundAllFiles) { // ok it was a filelist add them m_fileChooser.setSelectedFiles(selectedFiles.toArray(new File[] {})); onAdd(); } else { // make a new file filter final FileFilter ff = new FileFilter() { String filterString = WildcardMatcher.wildcardToRegex(text); @Override public boolean accept(final File f) { return f.getName().matches(filterString); } @Override public String getDescription() { return text; } }; FileFilter containedFilter = null; for (final FileFilter filter : m_fileChooser.getChoosableFileFilters()) { if (filter.getDescription().equals(ff.getDescription())) { containedFilter = filter; break; } } if (containedFilter == null) { m_fileChooser.setFileFilter(ff); } else { m_fileChooser.setFileFilter(containedFilter); } } } private void fireSelectionChangedEvent() { if (m_listeners != null) { for (final ChangeListener listener : m_listeners) { listener.stateChanged(new ChangeEvent(this)); } } m_selectedFileListBorder.setTitle("Selected files (" + m_selectedFileListModel.getSize() + ")"); repaint(); if (m_eventService != null) { m_eventService.publish(new FileChooserSelectedFilesChgEvent(getSelectedFiles())); } } public FileFilter getFileFilter() { return m_fileFilter; } /** * The list of the selected files * * @return files * */ public String[] getSelectedFiles() { final String[] values = new String[m_selectedFileListModel.getSize()]; for (int i = 0; i < values.length; i++) { values[i] = m_selectedFileListModel.getAbsolutePathAt(i); } return values; } public void onAdd() { final FileFilter ff = m_fileChooser.getFileFilter(); if (m_fileChooser.getSelectedFiles().length == 0) { JOptionPane.showMessageDialog(this, "No files selected. Please select at least one file or directory.", "Warning", JOptionPane.ERROR_MESSAGE); return; } m_selectedFileListModel.addFiles(m_fileChooser.getSelectedFiles(), ff); m_filePref.put("Path", m_fileChooser.getCurrentDirectory().toString()); fireSelectionChangedEvent(); } private void onAddAllTopLevelFiles() { final File[] files = m_fileChooser.getCurrentDirectory().listFiles(); final ArrayList<File> topLevelFiles = new ArrayList<File>(); // remove directories for (final File f : files) { if (!f.isDirectory() && m_fileChooser.getFileFilter().accept(f)) { topLevelFiles.add(f); } } m_selectedFileListModel.addFiles(topLevelFiles.toArray(new File[] {}), m_fileFilter); m_filePref.put("Path", m_fileChooser.getCurrentDirectory().toString()); fireSelectionChangedEvent(); } /* * when the remove button was pressed */ private void onRemove() { if (m_selectedFileListModel.getSize() == 0) { JOptionPane.showMessageDialog(this, "No files in list to Remove", "Error", JOptionPane.ERROR_MESSAGE); return; } m_selectedFileListModel.remove(m_selectedFileList.getSelectedIndices()); m_selectedFileList.clearSelection(); fireSelectionChangedEvent(); } /* * when the remove all button was pressed */ private void onRemoveAll() { m_selectedFileListModel.removeAll(); fireSelectionChangedEvent(); } /** * Updates the selected files list after removing or adding files * * @param selectedFiles */ public void update(final String[] selectedFiles) { // applying the model settings to the components if (selectedFiles.length > 0) { final File[] files = new File[selectedFiles.length]; for (int i = 0; i < selectedFiles.length; i++) { files[i] = new File(selectedFiles[i]); } m_selectedFileListModel.removeAll(); m_selectedFileListModel.addFiles(files, m_fileFilter); } if (m_eventService != null) { m_eventService.publish(new FileChooserSelectedFilesChgEvent(getSelectedFiles())); } } /** * On Mac OS X there is no text field for regexes with the default look and * feel {@link JFileChooser}. Therefore on Mac OS X the * {@link MacHackedFileChooserPanel} gets the * UIManager.getCrossPlatformLookAndFeelClassName() * * @return */ private MacHackedFileChooserPanel getFileChooserWithLookAndFeel() { MacHackedFileChooserPanel fileChooser = null; if (System.getProperty("os.name").equals("Mac OS X")) { LookAndFeel lookAndFeel = UIManager.getLookAndFeel(); try { UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); fileChooser = new MacHackedFileChooserPanel(); UIManager.setLookAndFeel(lookAndFeel); } catch (Exception e) { // mac hack } } return (fileChooser != null) ? fileChooser : new MacHackedFileChooserPanel(); } }