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