1 package com.hammurapi.jcapture; 2 import java.awt.BorderLayout; 3 import java.awt.Color; 4 import java.awt.Dimension; 5 import java.awt.Insets; 6 import java.awt.Point; 7 import java.awt.Rectangle; 8 import java.awt.Toolkit; 9 import java.awt.event.ActionEvent; 10 import java.awt.event.ComponentAdapter; 11 import java.awt.event.ComponentEvent; 12 import java.awt.event.ComponentListener; 13 import java.awt.image.BufferedImage; 14 import java.io.ByteArrayInputStream; 15 import java.io.ByteArrayOutputStream; 16 import java.io.IOException; 17 import java.text.SimpleDateFormat; 18 import java.util.Date; 19 import java.util.Properties; 20 import java.util.concurrent.atomic.AtomicBoolean; 21 22 import javax.imageio.ImageIO; 23 import javax.swing.AbstractAction; 24 import javax.swing.JButton; 25 import javax.swing.JFrame; 26 import javax.swing.JLabel; 27 import javax.swing.JOptionPane; 28 import javax.swing.JPanel; 29 import javax.swing.SwingUtilities; 30 import javax.swing.SwingWorker; 31 import javax.swing.border.LineBorder; 32 33 import netscape.javascript.JSObject; 34 35 import org.apache.http.HttpResponse; 36 import org.apache.http.HttpStatus; 37 38 /** 39 * License: LGPL. 40 * @author Pavel Vlasov. 41 * 42 */ 43 public class CaptureFrame extends javax.swing.JFrame { 44 private JPanel capturePanel; 45 private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss-SSS"); 46 private int counter; 47 private CaptureConfig captureConfig; 48 private AbstractCaptureApplet applet; 49 private JButton recordButton; 50 getCaptureConfig()51 public CaptureConfig getCaptureConfig() { 52 return captureConfig; 53 } 54 CaptureFrame(final AbstractCaptureApplet applet)55 public CaptureFrame(final AbstractCaptureApplet applet) throws Exception { 56 super("Screen capture"); 57 setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("camera.png"))); 58 59 setUndecorated(true); 60 61 Translucener.makeFrameTranslucent(this); 62 63 setAlwaysOnTop(true); 64 this.applet = applet; 65 captureConfig = new CaptureConfig(); 66 captureConfig.load(applet.loadConfig()); 67 captureConfig.setBackgroundProcessor(applet.getBackgroundProcessor()); 68 69 //--- GUI construction --- 70 71 capturePanel = new JPanel(); 72 73 final JLabel dimensionsLabel = new JLabel(""); 74 capturePanel.add(dimensionsLabel, BorderLayout.CENTER); 75 76 capturePanel.addComponentListener(new ComponentAdapter() { 77 78 @Override 79 public void componentResized(ComponentEvent e) { 80 super.componentResized(e); 81 dimensionsLabel.setText(e.getComponent().getWidth()+" x "+e.getComponent().getHeight()); 82 } 83 }); 84 85 JButton captureButton = new JButton(new AbstractAction() { 86 87 @Override 88 public void actionPerformed(ActionEvent e) { 89 Rectangle bounds = capturePanel.getBounds(); 90 Point loc = bounds.getLocation(); 91 SwingUtilities.convertPointToScreen(loc, capturePanel); 92 bounds.setLocation(loc); 93 Properties props = captureConfig.setRecordingRectangle(bounds); 94 if (props!=null) { 95 getApplet().storeConfig(props); 96 } 97 capturing.set(true); 98 setVisible(false); 99 } 100 101 }); 102 captureButton.setText("Capture"); 103 captureButton.setToolTipText("Create a snapshot of the screen"); 104 capturePanel.add(captureButton, BorderLayout.CENTER); 105 106 recordButton = new JButton(new AbstractAction() { 107 108 @Override 109 public void actionPerformed(ActionEvent e) { 110 Rectangle bounds = capturePanel.getBounds(); 111 Point loc = bounds.getLocation(); 112 SwingUtilities.convertPointToScreen(loc, capturePanel); 113 bounds.setLocation(loc); 114 Properties props = captureConfig.setRecordingRectangle(bounds); 115 if (props!=null) { 116 getApplet().storeConfig(props); 117 } 118 recording.set(true); 119 setVisible(false); 120 } 121 122 }); 123 recordButton.setText("Record"); 124 setRecordButtonState(); 125 capturePanel.add(recordButton, BorderLayout.CENTER); 126 127 JButton optionsButton = new JButton(new AbstractAction() { 128 129 @Override 130 public void actionPerformed(ActionEvent e) { 131 new CaptureOptionsDialog(CaptureFrame.this).setVisible(true); 132 } 133 134 }); 135 optionsButton.setText("Options"); 136 capturePanel.add(optionsButton, BorderLayout.CENTER); 137 138 JButton cancelButton = new JButton(new AbstractAction() { 139 140 @Override 141 public void actionPerformed(ActionEvent e) { 142 CaptureFrame.this.setVisible(false); 143 } 144 145 }); 146 cancelButton.setText("Cancel"); 147 capturePanel.add(cancelButton, BorderLayout.CENTER); 148 149 getContentPane().add(capturePanel, BorderLayout.CENTER); 150 151 capturePanel.setBorder(new LineBorder(new java.awt.Color(0,0,0), 1, false)); 152 153 if (captureConfig.getRecordingRectangle()==null) { 154 setSize(400, 300); 155 setLocationRelativeTo(null); 156 } else { 157 setBounds(captureConfig.getRecordingRectangle()); 158 } 159 160 Insets dragInsets = new Insets(5, 5, 5, 5); 161 new ComponentResizer(dragInsets, this); 162 163 ComponentMover cm = new ComponentMover(); 164 cm.registerComponent(this); 165 cm.setDragInsets(dragInsets); 166 167 addComponentListener(new ComponentListener() { 168 169 @Override 170 public void componentShown(ComponentEvent e) { 171 // TODO Auto-generated method stub 172 173 } 174 175 @Override 176 public void componentResized(ComponentEvent e) { 177 // TODO Auto-generated method stub 178 179 } 180 181 @Override 182 public void componentMoved(ComponentEvent e) { 183 // TODO Auto-generated method stub 184 185 } 186 187 @Override 188 public void componentHidden(ComponentEvent e) { 189 if (capturing.get()) { 190 capturing.set(false); 191 try { 192 capture(); 193 } catch (Exception ex) { 194 ex.printStackTrace(); 195 } 196 } else if (recording.get()) { 197 recording.set(false); 198 record(); 199 } 200 } 201 }); 202 203 } 204 setRecordButtonState()205 void setRecordButtonState() { 206 if (captureConfig.getEncoder()==null) { 207 recordButton.setEnabled(false); 208 recordButton.setToolTipText("Video format not selected. Use Options dialog to select video format."); 209 } else { 210 recordButton.setEnabled(true); 211 recordButton.setToolTipText("Record screen activity and audio"); 212 } 213 } 214 getApplet()215 public AbstractCaptureApplet getApplet() { 216 return applet; 217 } 218 capture()219 protected void capture() throws Exception { 220 try { 221 Thread.sleep(200); // For Ubuntu. 222 } catch (InterruptedException ie) { 223 // Ignore 224 } 225 226 BufferedImage screenShot = captureConfig.createScreenShot(null, null).call().getRegions().get(0).getImage().getImage(); 227 228 String prefix = getDatePrefix(); 229 230 String defaultImageFormat = applet.getParameter("imageFormat"); 231 if (defaultImageFormat==null || defaultImageFormat.trim().length()==0) { 232 defaultImageFormat = "PNG"; 233 } 234 final String defaultFileExtension=defaultImageFormat.toLowerCase(); 235 236 final String fileName = JOptionPane.showInputDialog(CaptureFrame.this, "Upload as", applet.getParameter("pageName")+"-capture-"+prefix+"-" + nextCounter() +"."+defaultFileExtension); 237 if (fileName!=null) { 238 try { 239 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 240 int idx = fileName.lastIndexOf('.'); 241 String imageFormat = idx==-1 ? defaultImageFormat : fileName.substring(idx+1).toUpperCase(); 242 ImageIO.write(screenShot, imageFormat, baos); 243 final byte[] imageBytes = baos.toByteArray(); 244 System.out.println("Image size: "+imageBytes.length); 245 // Uploading 246 SwingWorker<Boolean, Long> task = new SwingWorker<Boolean, Long>() { 247 248 @Override 249 protected Boolean doInBackground() throws Exception { 250 251 System.out.println("Uploading in background"); 252 try { 253 HttpResponse iResponse = applet.post( 254 CaptureFrame.this, 255 new ByteArrayInputStream(imageBytes), 256 imageBytes.length, 257 fileName, 258 "application/octet-stream"); 259 260 System.out.println("Response status line: "+iResponse.getStatusLine()); 261 if (iResponse.getStatusLine().getStatusCode()!=HttpStatus.SC_OK) { 262 errorMessage = iResponse.getStatusLine(); 263 errorTitle = "Error saving image"; 264 return false; 265 } 266 return true; 267 } catch (Error e) { 268 errorMessage=e.toString(); 269 errorTitle = "Upload error"; 270 e.printStackTrace(); 271 return false; 272 } 273 } 274 275 private Object errorMessage; 276 private String errorTitle; 277 278 protected void done() { 279 try { 280 if (get()) { 281 JSObject window = JSObject.getWindow(applet); 282 String toEval = "insertAtCarret('"+applet.getParameter("edid")+"','{{:"+fileName+"|}}')"; 283 System.out.println("Evaluating: "+toEval); 284 window.eval(toEval); 285 CaptureFrame.this.setVisible(false); 286 } else { 287 JOptionPane.showMessageDialog( 288 CaptureFrame.this, 289 errorMessage, 290 errorTitle, 291 JOptionPane.ERROR_MESSAGE); 292 } 293 } catch (Exception e) { 294 e.printStackTrace(); 295 JOptionPane.showMessageDialog( 296 CaptureFrame.this, 297 e.toString(), 298 "Exception", 299 JOptionPane.ERROR_MESSAGE); 300 } 301 }; 302 303 }; 304 305 task.execute(); 306 } catch (IOException ex) { 307 JOptionPane.showMessageDialog( 308 applet, 309 ex.toString(), 310 "Error saving image", 311 JOptionPane.ERROR_MESSAGE); 312 } 313 } 314 } 315 nextCounter()316 public int nextCounter() { 317 return counter++; 318 } 319 getDatePrefix()320 public String getDatePrefix() { 321 return dateFormat.format(new Date()); 322 } 323 record()324 protected void record() { 325 try { 326 Thread.sleep(200); // For Ubuntu. 327 } catch (InterruptedException ie) { 328 // Ignore 329 } 330 331 int borderWidth = 1; 332 JFrame[] borderFrames = new JFrame[4]; 333 334 Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); 335 336 Rectangle rr = captureConfig.getRecordingRectangle(); 337 Color borderColor = Color.RED; 338 if (rr.x>=borderWidth) { 339 // West border 340 borderFrames[0] = new JFrame(); 341 borderFrames[0].setDefaultCloseOperation(DISPOSE_ON_CLOSE); 342 borderFrames[0].setSize(borderWidth, rr.height+borderWidth*2); 343 borderFrames[0].setLocation(rr.x-borderWidth, rr.y-borderWidth); 344 borderFrames[0].setUndecorated(true); 345 borderFrames[0].setAlwaysOnTop(true); 346 borderFrames[0].setFocusableWindowState(false); 347 borderFrames[0].getContentPane().setBackground(borderColor); 348 } 349 if (rr.x+rr.width<dim.width-borderWidth) { 350 // East border 351 borderFrames[1] = new JFrame(); 352 borderFrames[1].setDefaultCloseOperation(DISPOSE_ON_CLOSE); 353 borderFrames[1].setSize(borderWidth, rr.height+borderWidth*2); 354 borderFrames[1].setLocation(rr.x+rr.width, rr.y-borderWidth); 355 borderFrames[1].setUndecorated(true); 356 borderFrames[1].setAlwaysOnTop(true); 357 borderFrames[1].setFocusableWindowState(false); 358 borderFrames[1].getContentPane().setBackground(borderColor); 359 } 360 if (rr.y>=borderWidth) { 361 // North border 362 borderFrames[2] = new JFrame(); 363 borderFrames[2].setDefaultCloseOperation(DISPOSE_ON_CLOSE); 364 borderFrames[2].setSize(rr.width, borderWidth); 365 borderFrames[2].setLocation(rr.x, rr.y-borderWidth); 366 borderFrames[2].setUndecorated(true); 367 borderFrames[2].setAlwaysOnTop(true); 368 borderFrames[2].setFocusableWindowState(false); 369 borderFrames[2].getContentPane().setBackground(borderColor); 370 } 371 if (rr.y+rr.height<dim.height-borderWidth) { 372 // South border 373 borderFrames[3] = new JFrame(); 374 borderFrames[3].setDefaultCloseOperation(DISPOSE_ON_CLOSE); 375 borderFrames[3].setSize(rr.width, borderWidth); 376 borderFrames[3].setLocation(rr.x, rr.y+rr.height); 377 borderFrames[3].setUndecorated(true); 378 borderFrames[3].setAlwaysOnTop(true); 379 borderFrames[3].setFocusableWindowState(false); 380 borderFrames[3].getContentPane().setBackground(borderColor); 381 } 382 383 RecordingControlsFrame inst = new RecordingControlsFrame(this, borderFrames); 384 int x = getLocation().x + getWidth() - inst.getWidth(); 385 if (x+inst.getWidth()>dim.getWidth()) { 386 x = dim.width-inst.getWidth(); 387 } else if (x<0) { 388 x = 0; 389 } 390 391 int y = rr.getLocation().y+getHeight()+1; 392 if (y+inst.getHeight()>dim.height) { 393 y = rr.getLocation().y-inst.getHeight(); 394 if (y<0) { 395 y=dim.height-inst.getHeight(); 396 } 397 } 398 inst.setLocation(x, y); 399 inst.setVisible(true); 400 } 401 402 private AtomicBoolean capturing = new AtomicBoolean(false); 403 private AtomicBoolean recording = new AtomicBoolean(false); 404 405 } 406