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}