001package jmri.util.swing;
002
003import java.awt.Desktop;
004import java.awt.event.ActionEvent;
005import java.io.BufferedInputStream;
006import java.io.File;
007import java.io.FileOutputStream;
008import java.io.IOException;
009import java.net.HttpURLConnection;
010import java.util.Arrays;
011
012import javax.swing.JMenuItem;
013import javax.swing.JPopupMenu;
014
015import jmri.util.FileUtil;
016import jmri.util.iharder.dnd.URIDrop;
017
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021public class EditableResizableImagePanel extends ResizableImagePanel implements URIDrop.Listener {
022
023    private transient MyMouseAdapter myMouseAdapter = null;
024    private String dropFolder;
025
026    /**
027     * Default constructor.
028     *
029     */
030    public EditableResizableImagePanel() {
031        super();
032        setDnd(true);
033    }
034
035    /**
036     * Constructor with initial image file path as parameter. Component will be
037     * (preferred) sized to image sized
038     *
039     * @param imagePath Path to image to display
040     */
041    public EditableResizableImagePanel(String imagePath) {
042        super(imagePath);
043        setDnd(true);
044    }
045
046    /**
047     * Constructor for DnDImagePanel with forced initial size
048     *
049     * @param imagePath Path to image to display
050     * @param w         Panel width
051     * @param h         Panel height
052     */
053    public EditableResizableImagePanel(String imagePath, int w, int h) {
054        super(imagePath, w, h);
055        setDnd(true);
056    }
057    
058    /**
059     * Cleanup the DnD from this component
060     *
061     */
062    public void removeDnd() {
063        URIDrop.remove(this);
064    }
065
066    /**
067     * Enable or disable drag'n drop, dropped files will be copied in latest
068     * used image path top folder when dnd enabled, also enable contextual menu
069     * with remove entry.
070     *
071     * @param dnd true to enable, false to disable
072     */
073    public final void setDnd(boolean dnd) {
074        if (dnd) {
075            new URIDrop(this, this);
076            if (myMouseAdapter == null) {
077                myMouseAdapter = new MyMouseAdapter(this);
078            }
079            addMouseListener(JmriMouseListener.adapt(myMouseAdapter));
080        } else {
081            URIDrop.remove(this);
082            if (myMouseAdapter != null) {
083                removeMouseListener(JmriMouseListener.adapt(myMouseAdapter));
084            }
085        }
086    }
087
088    /**
089     * Add a "open system file browser to path" menu item to the contextual menu
090     *
091     * @param menuEntry the menu entry string
092     * @param path the path to browse to
093     * @return the added menu item
094     */
095     public JMenuItem addMenuItemBrowseFolder(String menuEntry, String path) {
096        if (myMouseAdapter == null) {
097            return null;
098        }
099        JMenuItem  mi = new JMenuItem(menuEntry);
100        mi.addActionListener((ActionEvent e) -> {
101            try {
102                Desktop.getDesktop().open(new File(path));
103            } catch (IOException ex) {
104                log.error("Browse to action {}", ex.getMessage());
105            }
106        });
107        myMouseAdapter.addMenuItem(mi);
108        return mi;
109    }
110
111    /**
112     * Remove a given menu item from the contextual menu
113     *
114     * @param mi the JMenuItem to remove
115     */
116     public void removeMenuItemBrowseFolder(JMenuItem mi) {
117        if (myMouseAdapter == null) {
118            return ;
119        }
120        myMouseAdapter.removeMenuItem(mi);
121    }
122
123    //
124    // For contextual menu
125    private static class MyMouseAdapter implements JmriMouseListener {
126
127        private JPopupMenu popUpMenu;
128        private final JMenuItem removeMenuItem;
129
130        public MyMouseAdapter(ResizableImagePanel resizableImagePanel) {
131            popUpMenu = new JPopupMenu();
132            removeMenuItem = new JMenuItem(Bundle.getMessage("Remove"));
133            removeMenuItem.addActionListener((ActionEvent e) -> {
134                resizableImagePanel.setImagePath(null);
135            });
136            popUpMenu.add(removeMenuItem);
137        }
138
139        public void addMenuItem(JMenuItem item) {
140            if (item != null) {
141                popUpMenu.add(item);
142            }
143        }
144
145        public void removeMenuItem(JMenuItem item) {
146            if (item != null) {
147                popUpMenu.remove(item);
148            }
149        }
150
151        @Override
152        public void mouseClicked(JmriMouseEvent e) {
153            maybeShowPopup(e);
154        }
155
156        @Override
157        public void mousePressed(JmriMouseEvent e) {
158            maybeShowPopup(e);
159        }
160
161        @Override
162        public void mouseReleased(JmriMouseEvent e) {
163            maybeShowPopup(e);
164        }
165
166        @Override
167        public void mouseEntered(JmriMouseEvent e) {
168        }
169
170        @Override
171        public void mouseExited(JmriMouseEvent e) {
172        }
173
174        private void maybeShowPopup(JmriMouseEvent e) {
175            if (e.isPopupTrigger()) {
176                popUpMenu.show(e.getComponent(), e.getX(), e.getY());
177            }
178        }
179    }
180
181    public void setDropFolder(String s) {
182        dropFolder = s;
183    }
184
185    public String getDropFolder() {
186        return dropFolder;
187    }
188
189    /**
190     * Callback for the dnd listener
191     */
192    @Override
193    public void URIsDropped(java.net.URI[] uris) {
194        if (uris == null) {
195            log.error("URIsDropped: no URI");
196            return;
197        }
198        if (uris.length == 0) {
199            log.error("URIsDropped: no URI");
200            return;
201        }
202        if (uris[0].getPath() == null) {
203            log.error("URIsDropped: not a valid URI path: {}",uris[0]);
204            return;
205        }
206        File src = new File(uris[0].getPath());
207        File dest = new File(uris[0].getPath());
208        if (dropFolder != null) {
209            dest = new File(dropFolder + File.separatorChar + src.getName());
210            if (src.getParent().compareTo(dest.getParent()) != 0) {
211                // else case would be droping from dropFolder, so no copy
212                BufferedInputStream in = null;
213                BufferedInputStream out = null;
214                FileOutputStream fileOutputStream = null;
215                try {
216                    // prepare source reader
217                    boolean srcIsFile;
218                    FileUtil.createDirectory(dest.getParentFile().getPath());
219                    if (uris[0].getScheme() != null && (uris[0].getScheme().equals("content") || uris[0].getScheme().equals("file"))) {
220                        in = new BufferedInputStream(uris[0].toURL().openStream());
221                        srcIsFile = true;
222                    } else { // let's avoid some 403 by passing a user agent
223                        HttpURLConnection httpcon = (HttpURLConnection) uris[0].toURL().openConnection();
224                        httpcon.addRequestProperty("User-Agent", "Mozilla/4.0");
225                        in = new BufferedInputStream(httpcon.getInputStream());
226                        srcIsFile = false;
227                    }
228                    // guess destination name and check if does not already exist
229                    int i = 0;
230                    while (dest.exists()) {
231                        // is it already there?
232                        boolean alreadyThere = false;
233                        if ( (srcIsFile) && (dest.length() == src.length()) ) {
234                            out = new BufferedInputStream( dest.toURI().toURL().openStream() );
235                            byte dataBufferIn[] = new byte[4096];
236                            byte dataBufferOut[] = new byte[4096];
237                            int bytesReadIn;
238                            int bytesReadOut;
239                            alreadyThere = true;
240                            // file comparison loop
241                            while ((bytesReadIn = in.read(dataBufferIn, 0, 4096)) != -1) {
242                                bytesReadOut = out.read(dataBufferOut, 0, 4096);
243                                if ( (bytesReadIn != bytesReadOut) || ( ! Arrays.equals(dataBufferIn, dataBufferOut) ) ) {
244                                    alreadyThere = false;
245                                    break;
246                                }
247                            }
248                            out.close();
249                        }
250                        if (alreadyThere) {
251                            break;
252                        }
253                        // else try next one
254                        i++;
255                        dest = new File(dropFolder + File.separatorChar + i+"-"+src.getName());
256                    }
257                    // finally, if needed, create file and copy data
258                    if ( ! dest.exists()) {
259                        fileOutputStream = new FileOutputStream(dest);
260                        byte dataBuffer[] = new byte[4096];
261                        int bytesRead;
262                        // file copy loop
263                        while ((bytesRead = in.read(dataBuffer, 0, 4096)) != -1) {
264                            fileOutputStream.write(dataBuffer, 0, bytesRead);
265                        }
266                    }
267                } catch (IOException e) {
268                    log.error("URIsDropped: error while copying new file, using original file");
269                    log.error("URIsDropped: Error : {}", e.getMessage());
270                    log.error("URIsDropped: URI : {}", uris[0]);
271                    dest = src;
272                } finally {
273                    try {
274                        if (fileOutputStream != null) {
275                            fileOutputStream.close();
276                        }
277                    } catch (IOException ex) {
278                        log.error("URIsDropped: error while closing copy destination file : {}", ex.getMessage());
279                    }
280                    try {
281                        if (in != null) {
282                            in.close();
283                        }
284                    } catch (IOException ex) {
285                        log.error("URIsDropped: error while closing copy source file : {}", ex.getMessage());
286                    }
287                    try {
288                        if (out != null) {
289                            out.close();
290                        }
291                    } catch (IOException ex) {
292                        log.error("URIsDropped: error while closing duplicate check file : {}", ex.getMessage());
293                    }
294                }
295            }
296        }
297        setImagePath(dest.getPath());
298    }
299
300    private final static Logger log = LoggerFactory.getLogger(EditableResizableImagePanel.class);
301}