001 /* 002 * $Id: McIdasFrameDisplay.java,v 1.18 2012/02/19 17:35:51 davep Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2012 007 * Space Science and Engineering Center (SSEC) 008 * University of Wisconsin - Madison 009 * 1225 W. Dayton Street, Madison, WI 53706, USA 010 * https://www.ssec.wisc.edu/mcidas 011 * 012 * All Rights Reserved 013 * 014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 015 * some McIDAS-V source code is based on IDV and VisAD source code. 016 * 017 * McIDAS-V is free software; you can redistribute it and/or modify 018 * it under the terms of the GNU Lesser Public License as published by 019 * the Free Software Foundation; either version 3 of the License, or 020 * (at your option) any later version. 021 * 022 * McIDAS-V is distributed in the hope that it will be useful, 023 * but WITHOUT ANY WARRANTY; without even the implied warranty of 024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 025 * GNU Lesser Public License for more details. 026 * 027 * You should have received a copy of the GNU Lesser Public License 028 * along with this program. If not, see http://www.gnu.org/licenses. 029 */ 030 031 package edu.wisc.ssec.mcidasv.ui; 032 033 import java.awt.Component; 034 import java.awt.Dimension; 035 import java.awt.Font; 036 import java.awt.Image; 037 import java.awt.Insets; 038 import java.awt.MediaTracker; 039 import java.awt.event.ActionEvent; 040 import java.awt.event.ActionListener; 041 import java.awt.event.ItemEvent; 042 import java.awt.event.ItemListener; 043 import java.awt.event.KeyAdapter; 044 import java.awt.event.KeyEvent; 045 import java.awt.event.KeyListener; 046 import java.io.File; 047 import java.io.FileOutputStream; 048 import java.io.IOException; 049 import java.util.ArrayList; 050 import java.util.Hashtable; 051 import java.util.List; 052 import java.util.Vector; 053 054 import javax.swing.AbstractButton; 055 import javax.swing.BorderFactory; 056 import javax.swing.Icon; 057 import javax.swing.JButton; 058 import javax.swing.JCheckBox; 059 import javax.swing.JComboBox; 060 import javax.swing.JComponent; 061 import javax.swing.JLabel; 062 import javax.swing.JPanel; 063 import javax.swing.JRadioButton; 064 import javax.swing.JSlider; 065 import javax.swing.JTextField; 066 import javax.swing.border.EtchedBorder; 067 import javax.swing.event.ChangeEvent; 068 import javax.swing.event.ChangeListener; 069 070 import ucar.unidata.ui.AnimatedGifEncoder; 071 import ucar.unidata.ui.ImageUtils; 072 import ucar.unidata.ui.JpegImagesToMovie; 073 import ucar.unidata.util.FileManager; 074 import ucar.unidata.util.GuiUtils; 075 import ucar.unidata.util.LogUtil; 076 import ucar.unidata.util.Misc; 077 import ucar.unidata.util.Resource; 078 079 public class McIdasFrameDisplay extends JPanel implements ActionListener { 080 081 /** Do we show the big icon */ 082 public static boolean bigIcon = false; 083 084 /** The start/stop button */ 085 AbstractButton startStopBtn; 086 087 /** stop icon */ 088 private static Icon stopIcon; 089 090 /** start icon */ 091 private static Icon startIcon; 092 093 /** Flag for changing the INDEX */ 094 public static final String CMD_INDEX = "CMD_INDEX"; 095 096 /** property for setting the widget to the first frame */ 097 public static final String CMD_BEGINNING = "CMD_BEGINNING"; 098 099 /** property for setting the widget to the loop in reverse */ 100 public static final String CMD_BACKWARD = "CMD_BACKWARD"; 101 102 /** property for setting the widget to the start or stop */ 103 public static final String CMD_STARTSTOP = "CMD_STARTSTOP"; 104 105 /** property for setting the widget to the loop forward */ 106 public static final String CMD_FORWARD = "CMD_FORWARD"; 107 108 /** property for setting the widget to the last frame */ 109 public static final String CMD_END = "CMD_END"; 110 111 /** hi res button */ 112 private static JRadioButton hiBtn; 113 114 /** medium res button */ 115 private static JRadioButton medBtn; 116 117 /** low res button */ 118 private static JRadioButton lowBtn; 119 120 /** display rate field */ 121 private JTextField displayRateFld; 122 123 private Integer frameNumber = 1; 124 private Integer frameIndex = 0; 125 private List frameNumbers; 126 private Hashtable images; 127 private Image theImage; 128 private JPanelImage pi; 129 private JComboBox indicator; 130 private Dimension d; 131 132 private Thread loopThread; 133 private boolean isLooping = false; 134 private int loopDwell = 500; 135 136 private boolean antiAlias = false; 137 138 public McIdasFrameDisplay(List frameNumbers) { 139 this(frameNumbers, new Dimension(640, 480)); 140 } 141 142 public McIdasFrameDisplay(List frameNumbers, Dimension d) { 143 if (frameNumbers.size()<1) return; 144 this.frameIndex = 0; 145 this.frameNumbers = frameNumbers; 146 this.frameNumber = (Integer)frameNumbers.get(this.frameIndex); 147 this.images = new Hashtable(frameNumbers.size()); 148 this.d = d; 149 this.pi = new JPanelImage(); 150 this.pi.setFocusable(true); 151 this.pi.setSize(this.d); 152 this.pi.setPreferredSize(this.d); 153 this.pi.setMinimumSize(this.d); 154 this.pi.setMaximumSize(this.d); 155 156 String[] frameNames = new String[frameNumbers.size()]; 157 for (int i=0; i<frameNumbers.size(); i++) { 158 frameNames[i] = "Frame " + (Integer)frameNumbers.get(i); 159 } 160 indicator = new JComboBox(frameNames); 161 indicator.setFont(new Font("Dialog", Font.PLAIN, 9)); 162 indicator.setLightWeightPopupEnabled(false); 163 indicator.setVisible(true); 164 indicator.addActionListener(new ActionListener() { 165 public void actionPerformed(ActionEvent e) { 166 showIndexNumber(indicator.getSelectedIndex()); 167 } 168 }); 169 170 /* 171 // Create the File menu 172 JMenuBar menuBar = new JMenuBar(); 173 JMenu fileMenu = new JMenu("File"); 174 menuBar.add(fileMenu); 175 fileMenu.add(GuiUtils.makeMenuItem("Print...", this, 176 "doPrintImage", null, true)); 177 fileMenu.add(GuiUtils.makeMenuItem("Save image...", this, 178 "doSaveImageInThread")); 179 fileMenu.add(GuiUtils.makeMenuItem("Save movie...", this, 180 "doSaveMovieInThread")); 181 182 setTitle(title); 183 setJMenuBar(menuBar); 184 */ 185 186 JComponent controls = GuiUtils.hgrid( 187 GuiUtils.left(doMakeAntiAlias()), GuiUtils.right(doMakeVCR())); 188 add(GuiUtils.vbox(controls, pi)); 189 190 } 191 192 /** 193 * Make the UI for anti-aliasing controls 194 * 195 * @return UI as a Component 196 */ 197 private Component doMakeAntiAlias() { 198 JCheckBox newBox = new JCheckBox("Smooth images", antiAlias); 199 newBox.setToolTipText("Set to use anti-aliasing to smooth images when resizing to fit frame display"); 200 newBox.addItemListener(new ItemListener() { 201 public void itemStateChanged(ItemEvent e) { 202 JCheckBox myself = (JCheckBox)e.getItemSelectable(); 203 antiAlias = myself.isSelected(); 204 paintFrame(); 205 } 206 }); 207 return newBox; 208 } 209 210 /** 211 * Make the UI for VCR controls. 212 * 213 * @return UI as a Component 214 */ 215 private JComponent doMakeVCR() { 216 KeyListener listener = new KeyAdapter() { 217 public void keyPressed(KeyEvent e) { 218 char c = e.getKeyChar(); 219 if (e.isAltDown()) { 220 if (c == (char)'a') showFrameNext(); 221 else if (c == (char)'b') showFramePrevious(); 222 else if (c == (char)'l') toggleLoop(true); 223 } 224 } 225 }; 226 List buttonList = new ArrayList(); 227 indicator.addKeyListener(listener); 228 buttonList.add(GuiUtils.inset(indicator, new Insets(0, 0, 0, 2))); 229 String[][] buttonInfo = { 230 { "Go to first frame", CMD_BEGINNING, getIcon("Rewind") }, 231 { "One frame back", CMD_BACKWARD, getIcon("StepBack") }, 232 { "Run/Stop", CMD_STARTSTOP, getIcon("Play") }, 233 { "One frame forward", CMD_FORWARD, getIcon("StepForward") }, 234 { "Go to last frame", CMD_END, getIcon("FastForward") } 235 }; 236 237 for (int i = 0; i < buttonInfo.length; i++) { 238 JButton btn = GuiUtils.getImageButton(buttonInfo[i][2], getClass(), 2, 2); 239 btn.setToolTipText(buttonInfo[i][0]); 240 btn.setActionCommand(buttonInfo[i][1]); 241 btn.addActionListener(this); 242 btn.addKeyListener(listener); 243 btn.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)); 244 buttonList.add(btn); 245 if (i == 2) { 246 startStopBtn = btn; 247 } 248 } 249 250 JComponent sbtn = makeSlider(); 251 sbtn.addKeyListener(listener); 252 buttonList.add(sbtn); 253 254 JComponent contents = GuiUtils.hflow(buttonList, 1, 0); 255 256 updateRunButton(); 257 return contents; 258 } 259 260 /** 261 * Get the correct icon name based on whether we are in big icon mode 262 * 263 * @param name base name 264 * 265 * @return Full path to icon 266 */ 267 private String getIcon(String name) { 268 return "/auxdata/ui/icons/" + name + (bigIcon 269 ? "24" 270 : "16") + ".gif"; 271 } 272 273 /** 274 * Public by implementing ActionListener. 275 * 276 * @param e ActionEvent to check 277 */ 278 public void actionPerformed(ActionEvent e) { 279 actionPerformed(e.getActionCommand()); 280 } 281 282 /** 283 * Handle the action 284 * 285 * @param cmd The action 286 */ 287 private void actionPerformed(String cmd) { 288 if (cmd.equals(CMD_STARTSTOP)) { 289 toggleLoop(false); 290 } else if (cmd.equals(CMD_FORWARD)) { 291 showFrameNext(); 292 } else if (cmd.equals(CMD_BACKWARD)) { 293 showFramePrevious(); 294 } else if (cmd.equals(CMD_BEGINNING)) { 295 showFrameFirst(); 296 } else if (cmd.equals(CMD_END)) { 297 showFrameLast(); 298 } 299 } 300 301 /** 302 * Update the icon in the run button 303 */ 304 private void updateRunButton() { 305 if (stopIcon == null) { 306 stopIcon = Resource.getIcon(getIcon("Pause"), true); 307 startIcon = Resource.getIcon(getIcon("Play"), true); 308 } 309 if (startStopBtn != null) { 310 if (isLooping) { 311 startStopBtn.setIcon(stopIcon); 312 startStopBtn.setToolTipText("Stop animation"); 313 } else { 314 startStopBtn.setIcon(startIcon); 315 startStopBtn.setToolTipText("Start animation"); 316 } 317 } 318 } 319 320 public void setFrameImage(int inFrame, Image inImage) { 321 images.put("Frame " + inFrame, inImage); 322 } 323 324 private int getIndexPrevious() { 325 int thisIndex = frameIndex.intValue(); 326 if (thisIndex > 0) 327 thisIndex--; 328 else 329 thisIndex = frameNumbers.size() - 1; 330 return thisIndex; 331 } 332 333 private int getIndexNext() { 334 int thisIndex = frameIndex.intValue(); 335 if (thisIndex < frameNumbers.size() - 1) 336 thisIndex++; 337 else 338 thisIndex = 0; 339 return thisIndex; 340 } 341 342 public void showFramePrevious() { 343 showIndexNumber(getIndexPrevious()); 344 } 345 346 public void showFrameNext() { 347 showIndexNumber(getIndexNext()); 348 } 349 350 public void showFrameFirst() { 351 showIndexNumber(0); 352 } 353 354 public void showFrameLast() { 355 showIndexNumber(frameNumbers.size() - 1); 356 } 357 358 public void toggleLoop(boolean goFirst) { 359 if (isLooping) stopLoop(goFirst); 360 else startLoop(goFirst); 361 } 362 363 public void startLoop(boolean goFirst) { 364 // if (goFirst) showFrameFirst(); 365 loopThread = new Thread(new Runnable() { 366 public void run() { 367 runLoop(); 368 } 369 }); 370 loopThread.start(); 371 isLooping = true; 372 updateRunButton(); 373 } 374 375 public void stopLoop(boolean goFirst) { 376 loopThread = null; 377 isLooping = false; 378 if (goFirst) showFrameFirst(); 379 updateRunButton(); 380 } 381 382 private void runLoop() { 383 try { 384 Thread myThread = Thread.currentThread(); 385 while (myThread == loopThread) { 386 long sleepTime = (long)loopDwell; 387 showFrameNext(); 388 //Make sure we're sleeping for a minimum of 100ms 389 if (sleepTime < 100) { 390 sleepTime = 100; 391 } 392 Misc.sleep(sleepTime); 393 } 394 } catch (Exception e) { 395 LogUtil.logException("Loop animation: ", e); 396 } 397 } 398 399 private void showIndexNumber(int inIndex) { 400 if (inIndex < 0 || inIndex >= frameNumbers.size()) return; 401 frameIndex = (Integer)inIndex; 402 frameNumber = (Integer)frameNumbers.get(inIndex); 403 indicator.setSelectedIndex(frameIndex); 404 paintFrame(); 405 } 406 407 public void showFrameNumber(int inFrame) { 408 int inIndex = -1; 409 for (int i=0; i<frameNumbers.size(); i++) { 410 Integer frameInt = (Integer)frameNumbers.get(i); 411 if (frameInt.intValue() == inFrame) { 412 inIndex = (Integer)i; 413 break; 414 } 415 } 416 if (inIndex >= 0) 417 showIndexNumber(inIndex); 418 else 419 System.err.println("showFrameNumber: " + inFrame + " is not a valid frame"); 420 } 421 422 public int getFrameNumber() { 423 return frameNumber.intValue(); 424 } 425 426 private void paintFrame() { 427 theImage = (Image)images.get("Frame " + frameNumber); 428 if (theImage == null) { 429 System.err.println("paintFrame: Got a null image for frame " + frameNumber); 430 return; 431 } 432 433 MediaTracker mediaTracker = new MediaTracker(this); 434 mediaTracker.addImage(theImage, frameNumber); 435 try { 436 mediaTracker.waitForID(frameNumber); 437 } catch (InterruptedException ie) { 438 System.err.println("MediaTracker exception: " + ie); 439 } 440 441 this.pi.setImage(theImage); 442 this.pi.repaint(); 443 } 444 445 /** 446 * Make the value slider 447 * 448 * @return The slider button 449 */ 450 private JComponent makeSlider() { 451 ChangeListener listener = new ChangeListener() { 452 public void stateChanged(ChangeEvent e) { 453 JSlider slide = (JSlider) e.getSource(); 454 if (slide.getValueIsAdjusting()) { 455 // return; 456 } 457 loopDwell = slide.getValue() * 100; 458 } 459 }; 460 JComponent[] comps = GuiUtils.makeSliderPopup(1, 50, loopDwell / 100, listener); 461 comps[0].setToolTipText("Change dwell rate"); 462 return comps[0]; 463 } 464 465 /** 466 * Print the image 467 */ 468 /* 469 public void doPrintImage() { 470 try { 471 toFront(); 472 PrinterJob printJob = PrinterJob.getPrinterJob(); 473 printJob.setPrintable( 474 ((DisplayImpl) getMaster().getDisplay()).getPrintable()); 475 if ( !printJob.printDialog()) { 476 return; 477 } 478 printJob.print(); 479 } catch (Exception exc) { 480 logException("There was an error printing the image", exc); 481 } 482 } 483 */ 484 485 /** 486 * User has requested saving display as an image. Prompt 487 * for a filename and save the image to it. 488 */ 489 public void doSaveImageInThread() { 490 Misc.run(this, "doSaveImage"); 491 } 492 493 /** 494 * Save the image 495 */ 496 public void doSaveImage() { 497 498 SecurityManager backup = System.getSecurityManager(); 499 System.setSecurityManager(null); 500 try { 501 if (hiBtn == null) { 502 hiBtn = new JRadioButton("High", true); 503 medBtn = new JRadioButton("Medium", false); 504 lowBtn = new JRadioButton("Low", false); 505 GuiUtils.buttonGroup(hiBtn, medBtn).add(lowBtn); 506 } 507 JPanel qualityPanel = GuiUtils.vbox(new JLabel("Quality:"), 508 hiBtn, medBtn, lowBtn); 509 510 JComponent accessory = GuiUtils.vbox(Misc.newList(qualityPanel)); 511 512 List filters = Misc.newList(FileManager.FILTER_IMAGE); 513 514 String filename = FileManager.getWriteFile(filters, 515 FileManager.SUFFIX_JPG, 516 GuiUtils.top(GuiUtils.inset(accessory, 5))); 517 518 if (filename != null) { 519 if (filename.endsWith(".pdf")) { 520 ImageUtils.writePDF( 521 new FileOutputStream(filename), this.pi); 522 System.setSecurityManager(backup); 523 return; 524 } 525 float quality = 1.0f; 526 if (medBtn.isSelected()) { 527 quality = 0.6f; 528 } else if (lowBtn.isSelected()) { 529 quality = 0.2f; 530 } 531 ImageUtils.writeImageToFile(theImage, filename, quality); 532 } 533 } catch (Exception e) { 534 System.err.println("doSaveImage exception: " + e); 535 } 536 // for webstart 537 System.setSecurityManager(backup); 538 539 } 540 541 /** 542 * User has requested saving display as a movie. Prompt 543 * for a filename and save the images to it. 544 */ 545 public void doSaveMovieInThread() { 546 Misc.run(this, "doSaveMovie"); 547 } 548 549 /** 550 * Save the movie 551 */ 552 public void doSaveMovie() { 553 554 try { 555 Dimension size = new Dimension(); 556 List theImages = new ArrayList(frameNumbers.size()); 557 for (int i=0; i<frameNumbers.size(); i++) { 558 Integer frameInt = (Integer)frameNumbers.get(i); 559 theImages.add((Image)images.get("Frame " + frameInt)); 560 if (size == null) { 561 int width = theImage.getWidth(null); 562 int height = theImage.getHeight(null); 563 size = new Dimension(width, height); 564 } 565 } 566 567 //TODO: theImages should actually be a list of filenames that we have already saved 568 569 if (displayRateFld == null) { 570 displayRateFld = new JTextField("2", 3); 571 } 572 if (hiBtn == null) { 573 hiBtn = new JRadioButton("High", true); 574 medBtn = new JRadioButton("Medium", false); 575 lowBtn = new JRadioButton("Low", false); 576 GuiUtils.buttonGroup(hiBtn, medBtn).add(lowBtn); 577 } 578 JPanel qualityPanel = GuiUtils.vbox(new JLabel("Quality:"), 579 hiBtn, medBtn, lowBtn); 580 JPanel ratePanel = GuiUtils.vbox(new JLabel("Frames per second:"), 581 displayRateFld); 582 583 JComponent accessory = GuiUtils.vbox(Misc.newList(qualityPanel, 584 new JLabel(" "), ratePanel)); 585 586 List filters = Misc.newList(FileManager.FILTER_MOV, 587 FileManager.FILTER_AVI, FileManager.FILTER_ANIMATEDGIF); 588 589 String filename = FileManager.getWriteFile(filters, 590 FileManager.SUFFIX_MOV, 591 GuiUtils.top(GuiUtils.inset(accessory, 5))); 592 593 double displayRate = 594 (new Double(displayRateFld.getText())).doubleValue(); 595 596 if (filename.toLowerCase().endsWith(".gif")) { 597 double rate = 1.0 / displayRate; 598 AnimatedGifEncoder.createGif(filename, theImages, 599 AnimatedGifEncoder.REPEAT_FOREVER, 600 (int) (rate * 1000)); 601 } else if (filename.toLowerCase().endsWith(".avi")) { 602 ImageUtils.writeAvi(theImages, displayRate, 603 new File(filename)); 604 } else { 605 SecurityManager backup = System.getSecurityManager(); 606 System.setSecurityManager(null); 607 JpegImagesToMovie.createMovie(filename, size.width, 608 size.height, (int) displayRate, 609 new Vector(theImages)); 610 System.setSecurityManager(backup); 611 } 612 } catch (NumberFormatException nfe) { 613 LogUtil.userErrorMessage("Bad number format"); 614 return; 615 } catch (IOException ioe) { 616 LogUtil.userErrorMessage("Error writing movie: " + ioe); 617 return; 618 } 619 620 } 621 622 }