Skip to content

Commit 993b164

Browse files
authored
Merge pull request #27 from imagejan/multiple-file-input
Multiple file input
2 parents 6ba045d + 73281ec commit 993b164

File tree

4 files changed

+517
-1
lines changed

4 files changed

+517
-1
lines changed

pom.xml

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.scijava</groupId>
77
<artifactId>pom-scijava</artifactId>
8-
<version>12.0.0</version>
8+
<version>17.0.0</version>
99
<relativePath />
1010
</parent>
1111

@@ -59,6 +59,11 @@
5959
<url>http://imagej.net/User:Saalfeld</url>
6060
<properties><id>axtimwalde</id></properties>
6161
</contributor>
62+
<contributor>
63+
<name>Jan Eglinger</name>
64+
<url>http://imagej.net/User:Eglinger</url>
65+
<properties><id>imagejan</id></properties>
66+
</contributor>
6267
</contributors>
6368

6469
<mailingLists>
@@ -92,6 +97,7 @@
9297
<license.copyrightOwners>Board of Regents of the University of
9398
Wisconsin-Madison.</license.copyrightOwners>
9499
<license.projectName>SciJava UI components for Java Swing.</license.projectName>
100+
<scijava-common.version>2.66.0</scijava-common.version>
95101
</properties>
96102

97103
<repositories>

src/main/java/org/scijava/ui/swing/AbstractSwingUI.java

+52
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.awt.event.WindowAdapter;
3838
import java.awt.event.WindowEvent;
3939
import java.io.File;
40+
import java.io.FileFilter;
4041
import java.lang.reflect.InvocationTargetException;
4142

4243
import javax.swing.JFileChooser;
@@ -67,6 +68,7 @@
6768
import org.scijava.ui.swing.menu.SwingJMenuBarCreator;
6869
import org.scijava.ui.swing.menu.SwingJPopupMenuCreator;
6970
import org.scijava.ui.viewer.DisplayViewer;
71+
import org.scijava.widget.FileListWidget;
7072
import org.scijava.widget.FileWidget;
7173

7274
/**
@@ -161,6 +163,56 @@ public File chooseFile(final File file, final String style) {
161163
return result[0];
162164
}
163165

166+
@Override
167+
public File[] chooseFiles(final File parent, final File[] files, final FileFilter filter, final String style) {
168+
final File[][] result = new File[1][];
169+
try {
170+
// NB: We show the JFileChooser on the EDT because otherwise there could
171+
// be a deadlock, particularly on macOS.
172+
// See the {@link #chooseFile(File, String) chooseFile} method.
173+
threadService.invoke(() -> {
174+
final JFileChooser chooser = new JFileChooser(parent);
175+
chooser.setMultiSelectionEnabled(true);
176+
if (style.equals(FileListWidget.FILES_AND_DIRECTORIES)) {
177+
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
178+
}
179+
else if (style.equals(FileListWidget.DIRECTORIES_ONLY)) {
180+
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
181+
}
182+
else {
183+
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
184+
}
185+
chooser.setSelectedFiles(files);
186+
if (filter != null) {
187+
javax.swing.filechooser.FileFilter fileFilter = new javax.swing.filechooser.FileFilter() {
188+
189+
@Override
190+
public String getDescription() {
191+
return filter.toString();
192+
}
193+
194+
@Override
195+
public boolean accept(File f) {
196+
if (filter.accept(f)) return true;
197+
// directories should always be displayed
198+
// independent from selection mode
199+
return f.isDirectory();
200+
}
201+
};
202+
chooser.setFileFilter(fileFilter);
203+
}
204+
int rval = chooser.showOpenDialog(appFrame);
205+
if (rval == JFileChooser.APPROVE_OPTION) {
206+
result[0] = chooser.getSelectedFiles();
207+
}
208+
});
209+
}
210+
catch (final InvocationTargetException | InterruptedException exc) {
211+
log.error(exc);
212+
}
213+
return result[0];
214+
}
215+
164216
@Override
165217
public void showContextMenu(final String menuRoot, final Display<?> display,
166218
final int x, final int y)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
7+
* Institute of Molecular Cell Biology and Genetics, University of
8+
* Konstanz, and KNIME GmbH.
9+
* %%
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
*
13+
* 1. Redistributions of source code must retain the above copyright notice,
14+
* this list of conditions and the following disclaimer.
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
23+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
* #L%
31+
*/
32+
33+
package org.scijava.ui.swing.widget;
34+
35+
import java.awt.Dimension;
36+
import java.awt.event.ActionEvent;
37+
import java.awt.event.ActionListener;
38+
import java.awt.event.MouseEvent;
39+
import java.awt.event.MouseListener;
40+
import java.io.File;
41+
import java.io.FileFilter;
42+
import java.util.Collections;
43+
import java.util.List;
44+
45+
import javax.swing.Box;
46+
import javax.swing.DefaultListModel;
47+
import javax.swing.JButton;
48+
import javax.swing.JList;
49+
import javax.swing.JPanel;
50+
import javax.swing.JScrollPane;
51+
import javax.swing.TransferHandler;
52+
53+
import net.miginfocom.swing.MigLayout;
54+
55+
import org.scijava.log.LogService;
56+
import org.scijava.plugin.Parameter;
57+
import org.scijava.plugin.Plugin;
58+
import org.scijava.ui.UIService;
59+
import org.scijava.widget.FileListWidget;
60+
import org.scijava.widget.InputWidget;
61+
import org.scijava.widget.WidgetModel;
62+
63+
/**
64+
* Swing implementation of {@link FileListWidget}.
65+
*
66+
* @author Jan Eglinger
67+
*/
68+
@Plugin(type = InputWidget.class)
69+
public class SwingFileListWidget extends SwingInputWidget<File[]> implements
70+
FileListWidget<JPanel>, ActionListener, MouseListener {
71+
72+
@Parameter
73+
private UIService uiService;
74+
75+
@Parameter
76+
private LogService logService;
77+
78+
private JList<File> paths;
79+
private JButton addFilesButton;
80+
private JButton removeFilesButton;
81+
private JButton clearButton;
82+
83+
@Override
84+
public File[] getValue() {
85+
DefaultListModel<File> listModel = //
86+
(DefaultListModel<File>) paths.getModel();
87+
List<File> fileList = Collections.list(listModel.elements());
88+
return fileList.toArray(new File[fileList.size()]);
89+
}
90+
91+
@Override
92+
public void set(final WidgetModel model) {
93+
super.set(model);
94+
paths = new JList<>(new DefaultListModel<>());
95+
//paths.setMinimumSize(new Dimension(150, 50));
96+
//paths.setMaximumSize(new Dimension(150, 50));
97+
paths.setDragEnabled(true);
98+
final String style = model.getItem().getWidgetStyle();
99+
paths.setTransferHandler(new FileListTransferHandler(style));
100+
JScrollPane scrollPane = new JScrollPane(paths);
101+
scrollPane.setPreferredSize(new Dimension(350, 100));
102+
paths.addMouseListener(this);
103+
// scrollPane.addMouseListener(l);
104+
105+
setToolTip(scrollPane);
106+
getComponent().add(scrollPane);
107+
108+
getComponent().add(Box.createHorizontalStrut(3));
109+
110+
// GroupLayout buttonLayout = new GroupLayout(getComponent());
111+
JPanel buttonPanel = new JPanel(new MigLayout());
112+
113+
addFilesButton = new JButton("Add files...");
114+
setToolTip(addFilesButton);
115+
buttonPanel.add(addFilesButton, "wrap, grow");
116+
addFilesButton.addActionListener(this);
117+
118+
removeFilesButton = new JButton("Remove selected");
119+
setToolTip(removeFilesButton);
120+
buttonPanel.add(removeFilesButton, "wrap, grow");
121+
removeFilesButton.addActionListener(this);
122+
123+
clearButton = new JButton("Clear list");
124+
setToolTip(clearButton);
125+
buttonPanel.add(clearButton, "grow");
126+
clearButton.addActionListener(this);
127+
128+
getComponent().add(buttonPanel);
129+
130+
/*
131+
getComponent().setLayout(buttonLayout);
132+
buttonLayout.setVerticalGroup(buttonLayout.createSequentialGroup()
133+
.addComponent(addFilesButton)
134+
.addComponent(removeFilesButton)
135+
.addComponent(clearButton));
136+
*/
137+
138+
refreshWidget();
139+
}
140+
141+
// -- Typed methods --
142+
143+
@Override
144+
public boolean supports(final WidgetModel model) {
145+
return super.supports(model) && model.isType(File[].class);
146+
}
147+
148+
// -- ActionListener methods --
149+
150+
@Override
151+
public void actionPerformed(final ActionEvent e) {
152+
DefaultListModel<File> listModel = //
153+
(DefaultListModel<File>) paths.getModel();
154+
List<File> fileList = Collections.list(listModel.elements());
155+
156+
if (e.getSource() == addFilesButton) {
157+
// Add new files
158+
// parse style attribute to allow choosing
159+
// files and/or directories, and filter files
160+
final WidgetModel widgetModel = get();
161+
final String widgetStyle = widgetModel.getItem().getWidgetStyle();
162+
FileFilter filter = SwingFileWidget.createFileFilter(widgetStyle);
163+
164+
String style;
165+
if (widgetModel.isStyle(FileListWidget.FILES_AND_DIRECTORIES)) {
166+
style = FileListWidget.FILES_AND_DIRECTORIES;
167+
} else if (widgetModel.isStyle(FileListWidget.DIRECTORIES_ONLY)) {
168+
style = FileListWidget.DIRECTORIES_ONLY;
169+
} else {
170+
style = FileListWidget.FILES_ONLY; // default
171+
}
172+
173+
fileList = uiService.chooseFiles(null, fileList, filter, style);
174+
if (fileList == null)
175+
return;
176+
for (File file : fileList) {
177+
listModel.addElement(file);
178+
}
179+
} else if (e.getSource() == removeFilesButton) {
180+
// Remove selected files
181+
List<File> selected = paths.getSelectedValuesList();
182+
for (File f : selected) {
183+
listModel.removeElement(f);
184+
}
185+
} else if (e.getSource() == clearButton) {
186+
// Clear the file selection
187+
listModel.removeAllElements();
188+
}
189+
paths.setModel(listModel);
190+
updateModel();
191+
}
192+
193+
// -- AbstractUIInputWidget methods ---
194+
195+
@Override
196+
protected void doRefresh() {
197+
File[] files = (File[]) get().getValue();
198+
DefaultListModel<File> listModel = new DefaultListModel<>();
199+
if (files != null) {
200+
for (File file : files) {
201+
listModel.addElement(file);
202+
}
203+
}
204+
paths.setModel(listModel);
205+
}
206+
207+
@Override
208+
public void mouseClicked(MouseEvent e) {
209+
// handle double click
210+
if (e.getClickCount() == 2) {
211+
DefaultListModel<File> listModel = //
212+
(DefaultListModel<File>) paths.getModel();
213+
// Remove selected files
214+
List<File> selected = paths.getSelectedValuesList();
215+
for (File f : selected) {
216+
listModel.removeElement(f);
217+
}
218+
paths.setModel(listModel);
219+
updateModel();
220+
}
221+
}
222+
223+
@Override
224+
public void mousePressed(MouseEvent e) {
225+
// Nothing to do
226+
}
227+
228+
@Override
229+
public void mouseReleased(MouseEvent e) {
230+
// Nothing to do
231+
}
232+
233+
@Override
234+
public void mouseEntered(MouseEvent e) {
235+
// Nothing to do
236+
}
237+
238+
@Override
239+
public void mouseExited(MouseEvent e) {
240+
// Nothing to do
241+
}
242+
243+
// -- Helper classes --
244+
245+
private class FileListTransferHandler extends TransferHandler {
246+
247+
private final String style;
248+
249+
public FileListTransferHandler(final String style) {
250+
this.style = style;
251+
}
252+
253+
@Override
254+
public boolean canImport(final TransferHandler.TransferSupport support) {
255+
return SwingFileWidget.hasFiles(support);
256+
}
257+
258+
@SuppressWarnings("unchecked")
259+
@Override
260+
public boolean importData(final TransferHandler.TransferSupport support) {
261+
final List<File> allFiles = SwingFileWidget.getFiles(support);
262+
if (allFiles == null) return false;
263+
final FileFilter filter = SwingFileWidget.createFileFilter(style);
264+
final List<File> files = SwingFileWidget.filterFiles(allFiles, filter);
265+
if (allFiles.size() != files.size()) {
266+
logService.warn("Some files were excluded " +
267+
"for not matching the input requirements (" + style + ")");
268+
}
269+
final JList<File> jlist = (JList<File>) support.getComponent();
270+
final DefaultListModel<File> model = (DefaultListModel<File>) //
271+
jlist.getModel();
272+
files.forEach(f -> model.addElement(f));
273+
jlist.setModel(model);
274+
updateModel();
275+
return true;
276+
}
277+
}
278+
}

0 commit comments

Comments
 (0)