View Javadoc
1   /**
2    * Copyright 2011, Aiki IT, FotoRenamer
3    * <p/>
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * <p/>
8    * http://www.apache.org/licenses/LICENSE-2.0
9    * <p/>
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.aikiit.fotorenamer.gui;
17  
18  import de.aikiit.fotorenamer.exception.InvalidDirectoryException;
19  import de.aikiit.fotorenamer.exception.NoFilesFoundException;
20  import de.aikiit.fotorenamer.image.CreationDateFromExifImageRenamer;
21  import de.aikiit.fotorenamer.util.LocalizationHelper;
22  import org.apache.logging.log4j.LogManager;
23  import org.apache.logging.log4j.Logger;
24  import org.apache.logging.log4j.util.Strings;
25  
26  import javax.swing.*;
27  import java.awt.*;
28  import java.awt.datatransfer.DataFlavor;
29  import java.awt.dnd.DnDConstants;
30  import java.awt.dnd.DropTarget;
31  import java.awt.dnd.DropTargetDropEvent;
32  import java.awt.event.KeyAdapter;
33  import java.awt.event.KeyEvent;
34  import java.io.File;
35  import java.io.IOException;
36  
37  import static de.aikiit.fotorenamer.util.LocalizationHelper.getBundleString;
38  import static de.aikiit.fotorenamer.util.LocalizationHelper.getParameterizedBundleString;
39  
40  /**
41   * This component provides a means to select images that are to be renamed.
42   *
43   * @author hirsch, 13.10.2003
44   * @version 2004-01-08
45   */
46  class ImageDirectorySelector extends JPanel {
47      /**
48       * The logger of this class.
49       **/
50      private static final Logger LOG =
51              LogManager.getLogger(ImageDirectorySelector.class);
52  
53      /**
54       * Contains the selected directory as a text field or any user input.
55       */
56      private JTextField textField = null;
57      /**
58       * The UI's button to start directory selection.
59       */
60      private JButton browseButton = null;
61      /**
62       * An image icon that is displayed as part of the button.
63       */
64      private final ImageIcon imageIcon;
65  
66      /**
67       * Default constructor provides means to create an imageSelect with a given
68       * image icon that is able to only work on directories.
69       *
70       * @param icon This icon is used as a picture in the select
71       *             button.
72       */
73      ImageDirectorySelector(final ImageIcon icon) {
74          super();
75          this.imageIcon = icon;
76          init();
77      }
78  
79      /**
80       * Provides a means to disable this component
81       * (e.g. during run of file renaming).
82       *
83       * @param enable Enable/disable this component.
84       */
85      public final void setEnabled(final boolean enable) {
86          textField.setEnabled(enable);
87          browseButton.setEnabled(enable);
88      }
89  
90      /**
91       * This method is used as a blocking call until the user selects something
92       * in the UI.
93       *
94       * @return Returns whether anything is selected within the current
95       * configuration.
96       */
97      // TODO improve design, let class emit an event in case a directory was selected
98      public final boolean isWaiting() {
99          return Strings.isEmpty(getSelectedDirectory());
100     }
101 
102     /**
103      * Initialize internal UI components.
104      */
105     private void init() {
106         createBaseLayout();
107         initTextField();
108         makeTextFieldDragAndDropable();
109         makeTextFieldReactOnEnterOrCopyPasteFromMainUI();
110     }
111 
112     private void createBaseLayout() {
113         GridBagLayout grid = new GridBagLayout();
114         GridBagConstraints gbc = new GridBagConstraints();
115         gbc.insets = new Insets(0, 2, 0, 2);
116         setLayout(grid);
117 
118         // Add field.
119         gbc.gridx = 0;
120         gbc.gridy = 0;
121         gbc.gridheight = 1;
122         gbc.gridwidth = 2;
123         gbc.anchor = GridBagConstraints.WEST;
124         textField = new JTextField(60);
125         grid.setConstraints(textField, gbc);
126         add(textField);
127 
128         // Add button.
129         gbc.gridx = 2;
130         gbc.gridy = 0;
131         gbc.gridheight = 1;
132         gbc.gridwidth = 1;
133         gbc.anchor = GridBagConstraints.EAST;
134 
135         // show button
136         browseButton = this.imageIcon == null
137                 ? new JButton(
138                 getBundleString(
139                         "fotorenamer.ui.selector.title"))
140                 : new JButton(
141                 getBundleString(
142                         "fotorenamer.ui.selector.title"), this.imageIcon);
143         browseButton.setMnemonic(getBundleString("fotorenamer.ui.selector.title.mnemonic").charAt(0));
144         browseButton.setMargin(new Insets(1, 1, 1, 1));
145         grid.setConstraints(browseButton, gbc);
146         add(browseButton);
147     }
148 
149     private void initTextField() {
150         // Add action listener and take current contents of textfield as start directory
151         browseButton.addActionListener(event -> {
152 
153             String currentPath = getSelectedDirectory();
154             JFileChooser fileDlg = new JFileChooser(com.google.common.base.Strings.isNullOrEmpty(currentPath) ? null : currentPath);
155 
156             fileDlg.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
157             fileDlg.setDialogTitle(
158                     getBundleString(
159                             "fotorenamer.ui.selector.directory"));
160             fileDlg.setApproveButtonText(getBundleString(
161                     "fotorenamer.ui.selector.select"));
162 
163             if (fileDlg.showOpenDialog(this)
164                     == JFileChooser.APPROVE_OPTION) {
165                 // use getCanonicalPath() to avoid ..-path manipulations and
166                 // try to set the selected file in the UI
167                 try {
168                     textField.setText(
169                             fileDlg.getSelectedFile().getCanonicalPath());
170                 } catch (IOException ioe) {
171                     LOG.error("Error while selecting directory, extracted text is: "
172                             + textField.getText());
173                     LOG.error(ioe.getMessage());
174                 }
175             }
176         });
177     }
178 
179     private void makeTextFieldReactOnEnterOrCopyPasteFromMainUI() {
180         // make textfield react on Enter/copied over from MainUIWindow
181         textField.addKeyListener(new KeyAdapter() {
182             @Override
183             public void keyPressed(final KeyEvent e) {
184                 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
185 
186                     SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
187                         @Override
188                         protected Void doInBackground() {
189                             if (isWaiting()) {
190                                 showErrorPopup(getBundleString("fotorenamer.ui.error.nodirectory"), getBundleString("fotorenamer.ui.error.nodirectory.title"));
191                                 return null;
192                             }
193 
194                             // perform renaming
195                             try {
196                                 CreationDateFromExifImageRenamer renamer =
197                                         new CreationDateFromExifImageRenamer(getSelectedDirectory());
198                                 new Thread(renamer).start();
199                             } catch (InvalidDirectoryException uv) {
200                                 LOG.info("Invalid directory selected: {}", uv.getMessage());
201                                 showErrorPopup(getParameterizedBundleString("fotorenamer.ui.error.invaliddirectory", uv.getMessage()), getBundleString("fotorenamer.ui.error.invaliddirectory.title"));
202                             } catch (NoFilesFoundException kde) {
203                                 LOG.info("No files found in {}", kde.getMessage());
204                                 showErrorPopup(getParameterizedBundleString("fotorenamer.ui.error.nofiles", kde.getMessage()), getBundleString("fotorenamer.ui.error.nofiles.title"));
205                             }
206                             return null;
207                         }
208 
209                         @Override
210                         protected void done() {
211                             // TODO how can I communicate with the surrounding UI to block the user from pressing the buttons
212                             LOG.debug("Finished working, cannot reset UI from the selector itself. Should find a way to lock the startbutton somehow.");
213                         }
214                     };
215                     // Execute the SwingWorker; GUI will not freeze
216                     worker.execute();
217                 }
218             }
219 
220         });
221     }
222 
223     private void makeTextFieldDragAndDropable() {
224         // make textfield drag'n'dropable
225         textField.setDropTarget(new DropTarget() {
226             public synchronized void drop(DropTargetDropEvent evt) {
227                 try {
228                     evt.acceptDrop(DnDConstants.ACTION_COPY);
229                     Object transferData = evt
230                             .getTransferable().getTransferData(
231                                     DataFlavor.javaFileListFlavor);
232 
233                     if (transferData instanceof java.util.List) {
234                         //noinspection unchecked
235                         java.util.List<File> droppedFiles = (java.util.List<File>) transferData;
236                         if (!droppedFiles.isEmpty()) {
237                             for (File droppedFile : droppedFiles) {
238                                 if (droppedFile.isDirectory()) {
239                                     final String path = droppedFile.getAbsolutePath();
240                                     LOG.info("Drag'n'drop done for file: {} with {} element(s) received", path, droppedFiles.size());
241                                     textField.setText(path);
242                                     break;
243                                 }
244                             }
245                         }
246                     }
247 
248                 } catch (Exception ex) {
249                     LOG.info("Drag'd'drop did not work due to exception", ex);
250                 }
251             }
252         });
253     }
254 
255     void showErrorPopup(final String message, final String title) {
256         JOptionPane.showMessageDialog(null,
257                 message,
258                 title,
259                 JOptionPane.ERROR_MESSAGE);
260     }
261 
262     /**
263      * Current directory is the representation of this component.
264      *
265      * @return The currently selected directory.
266      */
267     final String getSelectedDirectory() {
268         String currentSelection = LocalizationHelper.removeCrLf(this.textField.getText());
269         if (!com.google.common.base.Strings.isNullOrEmpty(currentSelection)) {
270             currentSelection = currentSelection.replaceAll("~", System.getProperty("user.home"));
271             currentSelection = currentSelection.trim();
272             LOG.debug("User input transformed into {}", currentSelection);
273         }
274         return currentSelection;
275     }
276 }