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