3333package org .scijava .ui .swing .widget ;
3434
3535import java .awt .Dimension ;
36+ import java .awt .dnd .DnDConstants ;
37+ import java .awt .dnd .DropTarget ;
38+ import java .awt .dnd .DropTargetAdapter ;
39+ import java .awt .dnd .DropTargetDragEvent ;
40+ import java .awt .dnd .DropTargetDropEvent ;
41+ import java .awt .dnd .DropTargetEvent ;
3642import java .awt .event .ActionEvent ;
3743import java .awt .event .ActionListener ;
3844import java .awt .event .MouseEvent ;
3945import java .awt .event .MouseListener ;
4046import java .io .File ;
4147import java .io .FileFilter ;
48+ import java .io .IOException ;
49+ import java .nio .file .Files ;
50+ import java .util .ArrayList ;
51+ import java .util .Arrays ;
4252import java .util .Collections ;
4353import java .util .List ;
54+ import java .util .TooManyListenersException ;
4455
4556import javax .swing .Box ;
4657import javax .swing .DefaultListModel ;
5364import net .miginfocom .swing .MigLayout ;
5465
5566import org .scijava .log .LogService ;
67+ import org .scijava .module .ModuleService ;
5668import org .scijava .plugin .Parameter ;
5769import org .scijava .plugin .Plugin ;
70+ import org .scijava .thread .ThreadService ;
5871import org .scijava .ui .UIService ;
5972import org .scijava .widget .FileListWidget ;
73+ import org .scijava .widget .FileWidget ;
6074import org .scijava .widget .InputWidget ;
6175import org .scijava .widget .WidgetModel ;
6276
@@ -70,13 +84,17 @@ public class SwingFileListWidget extends SwingInputWidget<File[]> implements
7084 FileListWidget <JPanel >, ActionListener , MouseListener {
7185
7286 @ Parameter
73- private UIService uiService ;
87+ private LogService logService ;
7488
7589 @ Parameter
76- private LogService logService ;
90+ private ModuleService moduleService ;
91+
92+ @ Parameter
93+ private ThreadService threadService ;
7794
7895 private JList <File > paths ;
7996 private JButton addFilesButton ;
97+ private JButton addFolderButton ;
8098 private JButton removeFilesButton ;
8199 private JButton clearButton ;
82100
@@ -107,14 +125,34 @@ public void set(final WidgetModel model) {
107125
108126 getComponent ().add (Box .createHorizontalStrut (3 ));
109127
110- // GroupLayout buttonLayout = new GroupLayout(getComponent());
111128 JPanel buttonPanel = new JPanel (new MigLayout ());
112129
113- addFilesButton = new JButton ("Add files..." );
130+ // Set button label dependent on widget style
131+ String filesLabel = model .isStyle (DIRECTORIES_ONLY ) ? "Add folders..."
132+ : "Add files..." ;
133+ addFilesButton = new JButton (filesLabel );
114134 setToolTip (addFilesButton );
115135 buttonPanel .add (addFilesButton , "wrap, grow" );
116136 addFilesButton .addActionListener (this );
117137
138+ // Only show folder button if style allows files
139+ if (!model .isStyle (DIRECTORIES_ONLY )) {
140+ addFolderButton = new JButton ("Add folder content..." );
141+ setToolTip (addFolderButton );
142+ // add TransferHandler to accept dropped folders
143+ addFolderButton .setTransferHandler (new FolderTransferHandler ());
144+
145+ DropTarget dropTarget = addFolderButton .getDropTarget ();
146+ try {
147+ dropTarget .addDropTargetListener (new ButtonDropTargetListener ());
148+ } catch (TooManyListenersException exc ) {
149+ logService .error ("Error with setting up drop support" , exc );
150+ }
151+
152+ buttonPanel .add (addFolderButton , "wrap, grow" );
153+ addFolderButton .addActionListener (this );
154+ }
155+
118156 removeFilesButton = new JButton ("Remove selected" );
119157 setToolTip (removeFilesButton );
120158 buttonPanel .add (removeFilesButton , "wrap, grow" );
@@ -127,14 +165,6 @@ public void set(final WidgetModel model) {
127165
128166 getComponent ().add (buttonPanel );
129167
130- /*
131- getComponent().setLayout(buttonLayout);
132- buttonLayout.setVerticalGroup(buttonLayout.createSequentialGroup()
133- .addComponent(addFilesButton)
134- .addComponent(removeFilesButton)
135- .addComponent(clearButton));
136- */
137-
138168 refreshWidget ();
139169 }
140170
@@ -151,12 +181,12 @@ public boolean supports(final WidgetModel model) {
151181 public void actionPerformed (final ActionEvent e ) {
152182 DefaultListModel <File > listModel = //
153183 (DefaultListModel <File >) paths .getModel ();
154- List <File > fileList = Collections .list (listModel .elements ());
155184
156185 if (e .getSource () == addFilesButton ) {
157186 // Add new files
158187 // parse style attribute to allow choosing
159188 // files and/or directories, and filter files
189+ List <File > fileList = Collections .list (listModel .elements ());
160190 final WidgetModel widgetModel = get ();
161191 final String widgetStyle = widgetModel .getItem ().getWidgetStyle ();
162192 FileFilter filter = SwingFileWidget .createFileFilter (widgetStyle );
@@ -170,12 +200,16 @@ public void actionPerformed(final ActionEvent e) {
170200 style = FileListWidget .FILES_ONLY ; // default
171201 }
172202
173- fileList = uiService .chooseFiles (null , fileList , filter , style );
203+ fileList = ui () .chooseFiles (null , fileList , filter , style );
174204 if (fileList == null )
175205 return ;
176- for (File file : fileList ) {
177- listModel .addElement (file );
178- }
206+ fileList .forEach (file -> listModel .addElement (file ));
207+ } else if (e .getSource () == addFolderButton ) {
208+ File folder = ui ().chooseFile (null , FileWidget .DIRECTORY_STYLE );
209+ if (folder == null )
210+ return ;
211+ List <File > fileList = getFilesFromFolder (folder );
212+ fileList .forEach (file -> listModel .addElement (file ));
179213 } else if (e .getSource () == removeFilesButton ) {
180214 // Remove selected files
181215 List <File > selected = paths .getSelectedValuesList ();
@@ -240,6 +274,27 @@ public void mouseExited(MouseEvent e) {
240274 // Nothing to do
241275 }
242276
277+ // -- Helper methods --
278+
279+ private List <File > getFilesFromFolder (File inputFolder ) {
280+ // get files in folder and add to listModel
281+ List <File > fileList = new ArrayList <>();
282+ final WidgetModel widgetModel = get ();
283+ final String widgetStyle = widgetModel .getItem ().getWidgetStyle ();
284+ FileFilter filter = SwingFileWidget .createFileFilter (widgetStyle );
285+ try {
286+ fileList = Arrays
287+ .asList ((Files .walk (inputFolder .toPath ())
288+ .filter (path -> filter .accept (path .toFile ())))
289+ .map (path -> path .toFile ())
290+ .toArray (File []::new ));
291+ } catch (IOException exc ) {
292+ logService
293+ .error ("Error when trying to retrieve file list" , exc );
294+ }
295+ return fileList ;
296+ }
297+
243298 // -- Helper classes --
244299
245300 private class FileListTransferHandler extends TransferHandler {
@@ -275,4 +330,60 @@ public boolean importData(final TransferHandler.TransferSupport support) {
275330 return true ;
276331 }
277332 }
333+
334+ private class FolderTransferHandler extends TransferHandler {
335+ @ Override
336+ public boolean canImport (final TransferHandler .TransferSupport support ) {
337+ return SwingFileWidget .hasFiles (support );
338+ }
339+
340+ @ Override
341+ public boolean importData (final TransferHandler .TransferSupport support ) {
342+ // getFiles from support
343+ final List <File > files = SwingFileWidget .getFiles (support );
344+ if (files == null || files .size () != 1 )
345+ return false ;
346+
347+ // check if it's a folder
348+ final File folder = files .get (0 );
349+ if (!folder .isDirectory ())
350+ return false ;
351+
352+ // get all files matching filter from folder and subfolders
353+ List <File > fileList = getFilesFromFolder (folder );
354+
355+ // add files to model
356+ DefaultListModel <File > model = //
357+ (DefaultListModel <File >) paths .getModel ();
358+ fileList .forEach (file -> model .addElement (file ));
359+ paths .setModel (model );
360+ updateModel ();
361+ return true ;
362+ }
363+ }
364+
365+ private class ButtonDropTargetListener extends DropTargetAdapter {
366+
367+ @ Override
368+ public void drop (DropTargetDropEvent dtde ) {
369+ JButton button = (JButton ) dtde .getDropTargetContext ()
370+ .getComponent ();
371+ button .getModel ().setPressed (false );
372+ dtde .acceptDrop (DnDConstants .ACTION_COPY_OR_MOVE );
373+ }
374+
375+ @ Override
376+ public void dragEnter (DropTargetDragEvent dtde ) {
377+ JButton button = (JButton ) dtde .getDropTargetContext ()
378+ .getComponent ();
379+ button .getModel ().setPressed (true );
380+ }
381+
382+ @ Override
383+ public void dragExit (DropTargetEvent dte ) {
384+ JButton button = (JButton ) dte .getDropTargetContext ()
385+ .getComponent ();
386+ button .getModel ().setPressed (false );
387+ }
388+ }
278389}
0 commit comments