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