001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.gui.viewer; 020 021import java.awt.AlphaComposite; 022import java.awt.BorderLayout; 023import java.awt.Color; 024import java.awt.Dimension; 025import java.awt.Font; 026import java.awt.Graphics; 027import java.awt.Graphics2D; 028import java.awt.KeyboardFocusManager; 029import java.awt.RenderingHints; 030import java.awt.event.ActionEvent; 031import java.awt.event.ActionListener; 032import java.awt.event.KeyEvent; 033import java.awt.event.KeyListener; 034import java.awt.geom.Rectangle2D; 035import java.awt.image.BufferedImage; 036import java.beans.PropertyChangeEvent; 037import java.util.ArrayList; 038 039import javax.swing.ActionMap; 040import javax.swing.BorderFactory; 041import javax.swing.Box; 042import javax.swing.InputMap; 043import javax.swing.JComboBox; 044import javax.swing.JComponent; 045import javax.swing.JLabel; 046import javax.swing.JMenu; 047import javax.swing.JMenuItem; 048import javax.swing.JOptionPane; 049import javax.swing.JPanel; 050import javax.swing.JToolBar; 051import javax.swing.WindowConstants; 052import javax.swing.event.EventListenerList; 053 054import icy.action.CanvasActions; 055import icy.action.CanvasActions.ToggleLayersAction; 056import icy.action.SequenceOperationActions.ToggleVirtualSequenceAction; 057import icy.action.ViewerActions; 058import icy.action.WindowActions; 059import icy.canvas.IcyCanvas; 060import icy.canvas.IcyCanvas2D; 061import icy.canvas.IcyCanvasEvent; 062import icy.canvas.IcyCanvasListener; 063import icy.common.MenuCallback; 064import icy.common.listener.ProgressListener; 065import icy.gui.component.button.IcyButton; 066import icy.gui.component.button.IcyToggleButton; 067import icy.gui.component.renderer.LabelComboBoxRenderer; 068import icy.gui.dialog.ConfirmDialog; 069import icy.gui.dialog.MessageDialog; 070import icy.gui.dialog.SaverDialog; 071import icy.gui.frame.IcyFrame; 072import icy.gui.frame.IcyFrameAdapter; 073import icy.gui.frame.IcyFrameEvent; 074import icy.gui.frame.progress.ToolTipFrame; 075import icy.gui.lut.LUTViewer; 076import icy.gui.lut.abstract_.IcyLutViewer; 077import icy.gui.plugin.PluginComboBoxRenderer; 078import icy.gui.util.ComponentUtil; 079import icy.gui.viewer.ViewerEvent.ViewerEventType; 080import icy.image.IcyBufferedImage; 081import icy.image.cache.ImageCache; 082import icy.image.lut.LUT; 083import icy.main.Icy; 084import icy.plugin.PluginLoader; 085import icy.plugin.PluginLoader.PluginLoaderEvent; 086import icy.plugin.PluginLoader.PluginLoaderListener; 087import icy.plugin.interface_.PluginCanvas; 088import icy.preferences.GeneralPreferences; 089import icy.sequence.DimensionId; 090import icy.sequence.Sequence; 091import icy.sequence.SequenceEvent; 092import icy.sequence.SequenceListener; 093import icy.system.IcyExceptionHandler; 094import icy.system.IcyHandledException; 095import icy.system.thread.ThreadUtil; 096import icy.util.GraphicsUtil; 097import icy.util.Random; 098import icy.util.StringUtil; 099 100/** 101 * Viewer send an event if the IcyCanvas change. 102 * 103 * @author Fabrice de Chaumont & Stephane 104 */ 105public class Viewer extends IcyFrame implements KeyListener, SequenceListener, IcyCanvasListener, PluginLoaderListener 106{ 107 private class ViewerMainPanel extends JPanel 108 { 109 public ViewerMainPanel() 110 { 111 super(); 112 } 113 114 public void drawTextCenter(Graphics2D g, String text, float alpha) 115 { 116 final Rectangle2D rect = GraphicsUtil.getStringBounds(g, text); 117 final int w = (int) rect.getWidth(); 118 final int h = (int) rect.getHeight(); 119 final int x = (getWidth() - (w + 8 + 2)) / 2; 120 final int y = (getHeight() - (h + 8 + 2)) / 2; 121 122 g.setColor(Color.gray); 123 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); 124 g.fillRoundRect(x, y, w + 8, h + 8, 8, 8); 125 126 g.setColor(Color.white); 127 g.drawString(text, x + 4, y + 2 + h); 128 } 129 130 @Override 131 public void paint(Graphics g) 132 { 133 // currently modifying canvas ? 134 super.paint(g); 135 136 // display a message 137 if (settingCanvas) 138 { 139 final Graphics2D g2 = (Graphics2D) g.create(); 140 141 g2.setFont(new Font("Arial", Font.BOLD, 16)); 142 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 143 drawTextCenter(g2, "Loading canvas...", 0.8f); 144 g2.dispose(); 145 } 146 } 147 } 148 149 /** 150 * only show it once per Icy session 151 */ 152 private static boolean toolTipVirtualDone = false; 153 154 /** 155 * associated LUT 156 */ 157 private LUT lut; 158 /** 159 * associated canvas 160 */ 161 private IcyCanvas canvas; 162 /** 163 * associated sequence 164 */ 165 Sequence sequence; 166 167 /***/ 168 private final EventListenerList listeners = new EventListenerList(); 169 170 /** 171 * GUI 172 */ 173 private JToolBar toolBar; 174 private ViewerMainPanel mainPanel; 175 private LUTViewer lutViewer; 176 177 JComboBox canvasComboBox; 178 JComboBox lockComboBox; 179 IcyToggleButton layersEnabledButton; 180 IcyButton screenShotButton; 181 IcyButton screenShotAlternateButton; 182 IcyButton duplicateButton; 183 IcyButton switchStateButton; 184 IcyToggleButton virtualButton; 185 186 /** 187 * internals 188 */ 189 boolean initialized; 190 private final Runnable lutUpdater; 191 boolean settingCanvas; 192 193 public Viewer(Sequence sequence, boolean visible) 194 { 195 super("Viewer", true, true, true, true); 196 197 if (sequence == null) 198 throw new IllegalArgumentException("Can't open a null sequence."); 199 200 this.sequence = sequence; 201 202 // default 203 canvas = null; 204 lut = null; 205 lutViewer = null; 206 initialized = false; 207 settingCanvas = false; 208 209 lutUpdater = new Runnable() 210 { 211 @Override 212 public void run() 213 { 214 // don't need to update that too much 215 ThreadUtil.sleep(1); 216 217 ThreadUtil.invokeNow(new Runnable() 218 { 219 @Override 220 public void run() 221 { 222 final LUT lut = getLut(); 223 224 // closed --> ignore 225 if (lut != null) 226 { 227 // refresh LUT viewer 228 setLutViewer(new LUTViewer(Viewer.this, lut)); 229 // notify 230 fireViewerChanged(ViewerEventType.LUT_CHANGED); 231 } 232 } 233 }); 234 } 235 }; 236 237 mainPanel = new ViewerMainPanel(); 238 mainPanel.setLayout(new BorderLayout()); 239 240 // set menu directly in system menu so we don't need a extra MenuBar 241 setSystemMenuCallback(new MenuCallback() 242 { 243 @Override 244 public JMenu getMenu() 245 { 246 return Viewer.this.getMenu(); 247 } 248 }); 249 250 // build tool bar 251 buildToolBar(); 252 253 // create a new compatible LUT 254 final LUT lut = sequence.createCompatibleLUT(); 255 // restore user LUT if needed 256 if (sequence.hasUserLUT()) 257 { 258 final LUT userLut = sequence.getUserLUT(); 259 260 // restore colormaps (without alpha) 261 lut.setColorMaps(userLut, false); 262 // then restore scalers 263 lut.setScalers(userLut); 264 } 265 266 // set lut (this modify lutPanel) 267 setLut(lut); 268 // set default canvas to first available canvas plugin (Canvas2D should be first) 269 setCanvas(IcyCanvas.getCanvasPluginNames().get(0)); 270 271 setLayout(new BorderLayout()); 272 273 add(toolBar, BorderLayout.NORTH); 274 add(mainPanel, BorderLayout.CENTER); 275 276 // setting frame 277 refreshViewerTitle(); 278 setFocusable(true); 279 // set position depending window mode 280 setLocationInternal(20 + Random.nextInt(100), 20 + Random.nextInt(60)); 281 setLocationExternal(100 + Random.nextInt(200), 100 + Random.nextInt(150)); 282 setSize(640, 480); 283 284 // initial position in sequence 285 if (sequence.isEmpty()) 286 setPositionZ(0); 287 else 288 setPositionZ(((sequence.getSizeZ() + 1) / 2) - 1); 289 290 addFrameListener(new IcyFrameAdapter() 291 { 292 @Override 293 public void icyFrameOpened(IcyFrameEvent e) 294 { 295 if (!initialized) 296 { 297 if ((Viewer.this.sequence != null) && !Viewer.this.sequence.isEmpty()) 298 { 299 adjustViewerToImageSize(); 300 initialized = true; 301 } 302 } 303 } 304 305 @Override 306 public void icyFrameActivated(IcyFrameEvent e) 307 { 308 Icy.getMainInterface().setActiveViewer(Viewer.this); 309 } 310 311 @Override 312 public void icyFrameExternalized(IcyFrameEvent e) 313 { 314 refreshToolBar(); 315 } 316 317 @Override 318 public void icyFrameInternalized(IcyFrameEvent e) 319 { 320 refreshToolBar(); 321 } 322 323 @Override 324 public void icyFrameClosing(IcyFrameEvent e) 325 { 326 onClosing(); 327 } 328 }); 329 330 addKeyListener(this); 331 sequence.addListener(this); 332 PluginLoader.addListener(this); 333 334 // do this when viewer is initialized 335 Icy.getMainInterface().registerViewer(this); 336 // automatically add it to the desktop pane 337 addToDesktopPane(); 338 339 if (visible) 340 { 341 setVisible(true); 342 requestFocus(); 343 toFront(); 344 } 345 else 346 setVisible(false); 347 348 // can be done after setVisible 349 buildActionMap(); 350 } 351 352 public Viewer(Sequence sequence) 353 { 354 this(sequence, true); 355 } 356 357 void buildActionMap() 358 { 359 // global input map 360 buildActionMap(getInputMap(JComponent.WHEN_FOCUSED), getActionMap()); 361 } 362 363 protected void buildActionMap(InputMap imap, ActionMap amap) 364 { 365 imap.put(WindowActions.gridTileAction.getKeyStroke(), WindowActions.gridTileAction.getName()); 366 imap.put(WindowActions.horizontalTileAction.getKeyStroke(), WindowActions.horizontalTileAction.getName()); 367 imap.put(WindowActions.verticalTileAction.getKeyStroke(), WindowActions.verticalTileAction.getName()); 368 imap.put(CanvasActions.globalDisableSyncAction.getKeyStroke(), CanvasActions.globalDisableSyncAction.getName()); 369 imap.put(CanvasActions.globalSyncGroup1Action.getKeyStroke(), CanvasActions.globalSyncGroup1Action.getName()); 370 imap.put(CanvasActions.globalSyncGroup2Action.getKeyStroke(), CanvasActions.globalSyncGroup2Action.getName()); 371 imap.put(CanvasActions.globalSyncGroup3Action.getKeyStroke(), CanvasActions.globalSyncGroup3Action.getName()); 372 imap.put(CanvasActions.globalSyncGroup4Action.getKeyStroke(), CanvasActions.globalSyncGroup4Action.getName()); 373 374 amap.put(WindowActions.gridTileAction.getName(), WindowActions.gridTileAction); 375 amap.put(WindowActions.horizontalTileAction.getName(), WindowActions.horizontalTileAction); 376 amap.put(WindowActions.verticalTileAction.getName(), WindowActions.verticalTileAction); 377 amap.put(CanvasActions.globalDisableSyncAction.getName(), CanvasActions.globalDisableSyncAction); 378 amap.put(CanvasActions.globalSyncGroup1Action.getName(), CanvasActions.globalSyncGroup1Action); 379 amap.put(CanvasActions.globalSyncGroup2Action.getName(), CanvasActions.globalSyncGroup2Action); 380 amap.put(CanvasActions.globalSyncGroup3Action.getName(), CanvasActions.globalSyncGroup3Action); 381 amap.put(CanvasActions.globalSyncGroup4Action.getName(), CanvasActions.globalSyncGroup4Action); 382 } 383 384 /** 385 * Called when user want to close viewer 386 */ 387 protected void onClosing() 388 { 389 // this sequence was not saved 390 if ((sequence != null) && (sequence.getFilename() == null)) 391 { 392 // save new sequence enabled ? 393 if (GeneralPreferences.getSaveNewSequence()) 394 { 395 final int res = ConfirmDialog.confirmEx("Save sequence", 396 "Do you want to save '" + sequence.getName() + "' before closing it ?", 397 ConfirmDialog.YES_NO_CANCEL_OPTION); 398 399 switch (res) 400 { 401 case JOptionPane.YES_OPTION: 402 // save the image 403 new SaverDialog(sequence); 404 405 case JOptionPane.NO_OPTION: 406 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 407 break; 408 409 case JOptionPane.CANCEL_OPTION: 410 default: 411 setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 412 break; 413 } 414 415 return; 416 } 417 } 418 419 // just close it 420 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 421 } 422 423 /** 424 * Called when viewer is closed.<br> 425 * Release as much references we can here because of the 426 * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4759312"> 427 * JInternalFrame bug</a>. 428 */ 429 @Override 430 public void onClosed() 431 { 432 // notify close 433 fireViewerClosed(); 434 435 // remove listeners 436 sequence.removeListener(this); 437 if (canvas != null) 438 canvas.removeCanvasListener(this); 439 PluginLoader.removeListener(this); 440 441 icy.main.Icy.getMainInterface().unRegisterViewer(this); 442 443 // AWT JDesktopPane keep reference on last closed JInternalFrame 444 // it's good to free as much reference we can here 445 if (canvas != null) 446 canvas.shutDown(); 447 if (lutViewer != null) 448 lutViewer.dispose(); 449 if (mainPanel != null) 450 mainPanel.removeAll(); 451 if (toolBar != null) 452 toolBar.removeAll(); 453 454 // remove all listeners for this viewer 455 ViewerListener[] vls = listeners.getListeners(ViewerListener.class); 456 for (ViewerListener vl : vls) 457 listeners.remove(ViewerListener.class, vl); 458 459 lutViewer = null; 460 mainPanel = null; 461 462 canvas = null; 463 sequence = null; 464 lut = null; 465 toolBar = null; 466 canvasComboBox = null; 467 lockComboBox = null; 468 duplicateButton = null; 469 layersEnabledButton = null; 470 screenShotAlternateButton = null; 471 screenShotButton = null; 472 switchStateButton = null; 473 virtualButton = null; 474 475 super.onClosed(); 476 } 477 478 void adjustViewerToImageSize() 479 { 480 if (canvas instanceof IcyCanvas2D) 481 { 482 final IcyCanvas2D cnv = (IcyCanvas2D) canvas; 483 484 final int ix = cnv.getImageSizeX(); 485 final int iy = cnv.getImageSizeY(); 486 487 if ((ix > 0) && (iy > 0)) 488 { 489 // find scale factor to fit image in a 640x540 sized window 490 // and limit zoom to 100% 491 final double scale = Math.min(Math.min(640d / ix, 540d / iy), 1d); 492 493 cnv.setScaleX(scale); 494 cnv.setScaleY(scale); 495 496 // this actually resize viewer as canvas size depend from it 497 cnv.fitCanvasToImage(); 498 } 499 } 500 501 // minimum size to start : 400, 240 502 final Dimension size = new Dimension(Math.max(getWidth(), 400), Math.max(getHeight(), 240)); 503 // minimum size global : 200, 140 504 final Dimension minSize = new Dimension(200, 140); 505 506 // adjust size of both frames 507 setSizeExternal(size); 508 setSizeInternal(size); 509 setMinimumSizeInternal(minSize); 510 setMinimumSizeExternal(minSize); 511 } 512 513 /** 514 * Rebuild and return viewer menu 515 */ 516 JMenu getMenu() 517 { 518 final JMenu result = getDefaultSystemMenu(); 519 520 final JMenuItem overlayItem = new JMenuItem(CanvasActions.toggleLayersAction); 521 if ((canvas != null) && canvas.isLayersVisible()) 522 overlayItem.setText("Hide layers"); 523 else 524 overlayItem.setText("Show layers"); 525 final JMenuItem duplicateItem = new JMenuItem(ViewerActions.duplicateAction); 526 527 // set menu 528 result.insert(overlayItem, 0); 529 result.insertSeparator(1); 530 result.insert(duplicateItem, 2); 531 532 return result; 533 } 534 535 protected void buildLockCombo() 536 { 537 final ArrayList<JLabel> labels = new ArrayList<JLabel>(); 538 539 // get sync action labels 540 labels.add(CanvasActions.disableSyncAction.getLabelComponent(true, false)); 541 labels.add(CanvasActions.syncGroup1Action.getLabelComponent(true, false)); 542 labels.add(CanvasActions.syncGroup2Action.getLabelComponent(true, false)); 543 labels.add(CanvasActions.syncGroup3Action.getLabelComponent(true, false)); 544 labels.add(CanvasActions.syncGroup4Action.getLabelComponent(true, false)); 545 546 // build comboBox with lock id 547 lockComboBox = new JComboBox(labels.toArray()); 548 // set specific renderer 549 lockComboBox.setRenderer(new LabelComboBoxRenderer(lockComboBox)); 550 // limit size 551 ComponentUtil.setFixedWidth(lockComboBox, 48); 552 lockComboBox.setToolTipText("Select synchronisation group"); 553 // don't want focusable here 554 lockComboBox.setFocusable(false); 555 // needed because of VTK 556 lockComboBox.setLightWeightPopupEnabled(false); 557 558 // action on canvas change 559 lockComboBox.addActionListener(new ActionListener() 560 { 561 @Override 562 public void actionPerformed(ActionEvent e) 563 { 564 // adjust lock id 565 setViewSyncId(lockComboBox.getSelectedIndex()); 566 } 567 }); 568 } 569 570 void buildCanvasCombo() 571 { 572 // build comboBox with canvas plugins 573 canvasComboBox = new JComboBox(IcyCanvas.getCanvasPluginNames().toArray()); 574 // specific renderer 575 canvasComboBox.setRenderer(new PluginComboBoxRenderer(canvasComboBox, false)); 576 // limit size 577 ComponentUtil.setFixedWidth(canvasComboBox, 48); 578 canvasComboBox.setToolTipText("Select canvas type"); 579 // don't want focusable here 580 canvasComboBox.setFocusable(false); 581 // needed because of VTK 582 canvasComboBox.setLightWeightPopupEnabled(false); 583 584 // action on canvas change 585 canvasComboBox.addActionListener(new ActionListener() 586 { 587 @Override 588 public void actionPerformed(ActionEvent e) 589 { 590 // set selected canvas 591 setCanvas((String) canvasComboBox.getSelectedItem()); 592 } 593 }); 594 } 595 596 /** 597 * build the toolBar 598 */ 599 protected void buildToolBar() 600 { 601 // build combo box 602 buildLockCombo(); 603 buildCanvasCombo(); 604 605 // build buttons 606 layersEnabledButton = new IcyToggleButton(new ToggleLayersAction(true)); 607 layersEnabledButton.setHideActionText(true); 608 layersEnabledButton.setFocusable(false); 609 layersEnabledButton.setSelected(true); 610 screenShotButton = new IcyButton(CanvasActions.screenShotAction); 611 screenShotButton.setFocusable(false); 612 screenShotButton.setHideActionText(true); 613 screenShotAlternateButton = new IcyButton(CanvasActions.screenShotAlternateAction); 614 screenShotAlternateButton.setFocusable(false); 615 screenShotAlternateButton.setHideActionText(true); 616 duplicateButton = new IcyButton(ViewerActions.duplicateAction); 617 duplicateButton.setFocusable(false); 618 duplicateButton.setHideActionText(true); 619 // duplicateButton.setToolTipText("Duplicate view (no data duplication)"); 620 switchStateButton = new IcyButton(getSwitchStateAction()); 621 switchStateButton.setFocusable(false); 622 switchStateButton.setHideActionText(true); 623 virtualButton = new IcyToggleButton(new ToggleVirtualSequenceAction(false)); 624 virtualButton.setFocusable(false); 625 virtualButton.setHideActionText(true); 626 627 // and build the toolbar 628 toolBar = new JToolBar(); 629 toolBar.setFloatable(false); 630 // so we don't have any border 631 toolBar.setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 0)); 632 ComponentUtil.setPreferredHeight(toolBar, 26); 633 634 updateToolbarComponents(); 635 } 636 637 void updateToolbarComponents() 638 { 639 if (toolBar != null) 640 { 641 toolBar.removeAll(); 642 643 toolBar.add(lockComboBox); 644 toolBar.addSeparator(); 645 toolBar.add(canvasComboBox); 646 toolBar.addSeparator(); 647 toolBar.add(layersEnabledButton); 648 if (canvas != null) 649 canvas.customizeToolbar(toolBar); 650 toolBar.add(Box.createHorizontalGlue()); 651 toolBar.addSeparator(); 652 toolBar.add(screenShotButton); 653 toolBar.add(screenShotAlternateButton); 654 toolBar.addSeparator(); 655 toolBar.add(duplicateButton); 656 toolBar.add(switchStateButton); 657 toolBar.addSeparator(); 658 toolBar.add(virtualButton); 659 } 660 } 661 662 void refreshLockCombo() 663 { 664 final int syncId = getViewSyncId(); 665 666 lockComboBox.setEnabled(isSynchronizedViewSupported()); 667 lockComboBox.setSelectedIndex(syncId); 668 669 switch (syncId) 670 { 671 case 0: 672 lockComboBox.setBackground(Color.gray); 673 lockComboBox.setToolTipText("Synchronization disabled"); 674 break; 675 676 case 1: 677 lockComboBox.setBackground(Color.green); 678 lockComboBox.setToolTipText("Full synchronization group 1 (view and Z/T position)"); 679 break; 680 681 case 2: 682 lockComboBox.setBackground(Color.yellow); 683 lockComboBox.setToolTipText("Full synchronization group 2 (view and Z/T position)"); 684 break; 685 686 case 3: 687 lockComboBox.setBackground(Color.blue); 688 lockComboBox.setToolTipText("View synchronization group (view synched but not Z/T position)"); 689 break; 690 691 case 4: 692 lockComboBox.setBackground(Color.red); 693 lockComboBox.setToolTipText("Slice synchronization group (Z/T position synched but not view)"); 694 break; 695 } 696 } 697 698 void refreshCanvasCombo() 699 { 700 if (canvas != null) 701 { 702 // get plugin class name for this canvas 703 final String pluginName = IcyCanvas.getPluginClassName(canvas.getClass().getName()); 704 705 if (pluginName != null) 706 { 707 // align canvas combo to plugin name 708 if (!canvasComboBox.getSelectedItem().equals(pluginName)) 709 canvasComboBox.setSelectedItem(pluginName); 710 } 711 } 712 } 713 714 void refreshToolBar() 715 { 716 // FIXME : switchStateButton stay selected after action 717 final boolean layersVisible = (canvas != null) ? canvas.isLayersVisible() : false; 718 719 layersEnabledButton.setSelected(layersVisible); 720 if (layersVisible) 721 layersEnabledButton.setToolTipText("Hide layers"); 722 else 723 layersEnabledButton.setToolTipText("Show layers"); 724 725 final Sequence seq = getSequence(); 726 final boolean virtual = (seq != null) && seq.isVirtual(); 727 // update virtual state 728 virtualButton.setSelected(virtual); 729 if (!ImageCache.isEnabled()) 730 virtualButton.setToolTipText("Image cache is disabled, cannot use virtual sequence"); 731 else 732 { 733 if (virtual) 734 { 735 virtualButton.setToolTipText("Disable virtual sequence (caching)"); 736 737 // virtual was enabled for this sequence --> show tooltip to explain 738 if (!toolTipVirtualDone) 739 { 740 final ToolTipFrame tooltip = new ToolTipFrame("<html>" + "<img src=\"" 741 + Icy.class.getResource("/res/image/help/viewer_virtual.jpg").toString() + "\" /><br>" 742 + "<b>Your image has been made <i>virtual</i></b>.<br> This means that its data can be stored on disk to spare memory but this is at the cost of slower display / processing.<br>" 743 + "Also you should note that <b>some plugins aren't compatible with <i>virtual</i> images</b> and so the result may be inconsistent (possible data lost)." 744 + "</html>", 30, "viewerVirtual"); 745 tooltip.setSize(400, 180); 746 toolTipVirtualDone = true; 747 } 748 } 749 else 750 virtualButton.setToolTipText("Enable virtual sequence (caching)"); 751 } 752 753 // refresh combos 754 refreshLockCombo(); 755 refreshCanvasCombo(); 756 } 757 758 /** 759 * @return the sequence 760 */ 761 public Sequence getSequence() 762 { 763 return sequence; 764 } 765 766 /** 767 * Set the specified LUT for the viewer. 768 */ 769 public void setLut(LUT value) 770 { 771 if ((lut != value) && sequence.isLutCompatible(value)) 772 { 773 // set new lut & notify change 774 lut = value; 775 lutChanged(); 776 } 777 } 778 779 /** 780 * Returns the viewer LUT 781 */ 782 public LUT getLut() 783 { 784 // have to test this as we release sequence reference on closed 785 if (sequence == null) 786 return lut; 787 788 // sequence can be asynchronously modified so we have to test change on Getter 789 if ((lut == null) || !sequence.isLutCompatible(lut)) 790 { 791 // sequence type has changed, we need to recreate a compatible LUT 792 final LUT newLut = sequence.createCompatibleLUT(); 793 794 // keep the color map of previous LUT if they have the same number of channels 795 if ((lut != null) && (lut.getNumChannel() == newLut.getNumChannel())) 796 newLut.getColorSpace().setColorMaps(lut.getColorSpace(), true); 797 798 // set the new lut 799 setLut(newLut); 800 } 801 802 return lut; 803 } 804 805 /** 806 * Set the specified canvas for the viewer (from the {@link PluginCanvas} class name). 807 * 808 * @see IcyCanvas#getCanvasPluginNames() 809 */ 810 public void setCanvas(String pluginClassName) 811 { 812 // not the same canvas ? 813 if ((canvas == null) || !canvas.getClass().getName().equals(IcyCanvas.getCanvasClassName(pluginClassName))) 814 { 815 try 816 { 817 IcyCanvas newCanvas; 818 819 settingCanvas = true; 820 // show loading message 821 mainPanel.paintImmediately(mainPanel.getBounds()); 822 try 823 { 824 // try to create the new canvas 825 newCanvas = IcyCanvas.create(pluginClassName, this); 826 } 827 catch (Throwable e) 828 { 829 if (e instanceof IcyHandledException) 830 { 831 // just ignore 832 } 833 else if (e instanceof UnsupportedOperationException) 834 { 835 MessageDialog.showDialog(e.getLocalizedMessage(), MessageDialog.ERROR_MESSAGE); 836 } 837 else if (e instanceof Exception) 838 { 839 IcyExceptionHandler.handleException( 840 new ClassNotFoundException( 841 "Cannot find '" + pluginClassName + "' class --> cannot create the canvas.", e), 842 true); 843 } 844 else 845 IcyExceptionHandler.handleException(e, true); 846 847 // create a new instance of current canvas 848 newCanvas = IcyCanvas.create(canvas.getClass().getName(), this); 849 } 850 finally 851 { 852 settingCanvas = false; 853 } 854 855 final int saveX; 856 final int saveY; 857 final int saveZ; 858 final int saveT; 859 final int saveC; 860 861 // save properties and shutdown previous canvas 862 if (canvas != null) 863 { 864 // save position 865 saveX = canvas.getPositionX(); 866 saveY = canvas.getPositionY(); 867 saveZ = canvas.getPositionZ(); 868 saveT = canvas.getPositionT(); 869 saveC = canvas.getPositionC(); 870 871 canvas.removePropertyChangeListener(IcyCanvas.PROPERTY_LAYERS_VISIBLE, this); 872 canvas.removeCanvasListener(this); 873 // --> this actually can do some restore operation (as the palette) after creation of the new canvas 874 canvas.shutDown(); 875 // remove from mainPanel 876 mainPanel.remove(canvas); 877 } 878 else 879 saveX = saveY = saveZ = saveT = saveC = -1; 880 881 // prepare new canvas 882 newCanvas.addCanvasListener(this); 883 newCanvas.addPropertyChangeListener(IcyCanvas.PROPERTY_LAYERS_VISIBLE, this); 884 // add to mainPanel 885 mainPanel.add(newCanvas, BorderLayout.CENTER); 886 887 // restore position 888 if (saveX != -1) 889 newCanvas.setPositionX(saveX); 890 if (saveY != -1) 891 newCanvas.setPositionY(saveY); 892 if (saveZ != -1) 893 newCanvas.setPositionZ(saveZ); 894 if (saveT != -1) 895 newCanvas.setPositionT(saveT); 896 if (saveC != -1) 897 newCanvas.setPositionC(saveC); 898 899 // canvas set :) 900 canvas = newCanvas; 901 } 902 catch (Throwable e) 903 { 904 IcyExceptionHandler.handleException(e, true); 905 } 906 907 mainPanel.revalidate(); 908 909 // refresh viewer menu (so overlay checkbox is correctly set) 910 updateSystemMenu(); 911 updateToolbarComponents(); 912 refreshToolBar(); 913 914 // fix the OSX lost keyboard focus on canvas change in detached mode. 915 KeyboardFocusManager.getCurrentKeyboardFocusManager().upFocusCycle(getCanvas()); 916 917 // notify canvas changed to listener 918 fireViewerChanged(ViewerEventType.CANVAS_CHANGED); 919 } 920 } 921 922 /** 923 * @deprecated Use {@link #setCanvas(String)} instead. 924 */ 925 @Deprecated 926 public void setCanvas(IcyCanvas value) 927 { 928 if (canvas == value) 929 return; 930 931 final int saveX; 932 final int saveY; 933 final int saveZ; 934 final int saveT; 935 final int saveC; 936 937 if (canvas != null) 938 { 939 // save position 940 saveX = canvas.getPositionX(); 941 saveY = canvas.getPositionY(); 942 saveZ = canvas.getPositionZ(); 943 saveT = canvas.getPositionT(); 944 saveC = canvas.getPositionC(); 945 946 canvas.removePropertyChangeListener(IcyCanvas.PROPERTY_LAYERS_VISIBLE, this); 947 canvas.removeCanvasListener(this); 948 canvas.shutDown(); 949 // remove from mainPanel 950 mainPanel.remove(canvas); 951 } 952 else 953 saveX = saveY = saveZ = saveT = saveC = -1; 954 955 // set new canvas 956 canvas = value; 957 958 if (canvas != null) 959 { 960 canvas.addCanvasListener(this); 961 canvas.addPropertyChangeListener(IcyCanvas.PROPERTY_LAYERS_VISIBLE, this); 962 // add to mainPanel 963 mainPanel.add(canvas, BorderLayout.CENTER); 964 965 // restore position 966 if (saveX != -1) 967 canvas.setPositionX(saveX); 968 if (saveY != -1) 969 canvas.setPositionY(saveY); 970 if (saveZ != -1) 971 canvas.setPositionZ(saveZ); 972 if (saveT != -1) 973 canvas.setPositionT(saveT); 974 if (saveC != -1) 975 canvas.setPositionC(saveC); 976 } 977 978 mainPanel.revalidate(); 979 980 // refresh viewer menu (so overlay checkbox is correctly set) 981 updateSystemMenu(); 982 updateToolbarComponents(); 983 refreshToolBar(); 984 985 // fix the OSX lost keyboard focus on canvas change in detached mode. 986 KeyboardFocusManager.getCurrentKeyboardFocusManager().upFocusCycle(getCanvas()); 987 988 // notify canvas changed to listener 989 fireViewerChanged(ViewerEventType.CANVAS_CHANGED); 990 } 991 992 /** 993 * Returns true if the viewer initialization (correct image resizing) is completed. 994 */ 995 public boolean isInitialized() 996 { 997 return initialized; 998 } 999 1000 /** 1001 * Return the viewer Canvas object 1002 */ 1003 public IcyCanvas getCanvas() 1004 { 1005 return canvas; 1006 } 1007 1008 /** 1009 * Return the viewer Canvas panel 1010 */ 1011 public JPanel getCanvasPanel() 1012 { 1013 if (canvas != null) 1014 return canvas.getPanel(); 1015 1016 return null; 1017 } 1018 1019 /** 1020 * Return the viewer Lut panel 1021 */ 1022 public LUTViewer getLutViewer() 1023 { 1024 return lutViewer; 1025 } 1026 1027 /** 1028 * Set the {@link LUTViewer} for this viewer. 1029 */ 1030 public void setLutViewer(LUTViewer value) 1031 { 1032 if (lutViewer != value) 1033 { 1034 if (lutViewer != null) 1035 lutViewer.dispose(); 1036 lutViewer = value; 1037 } 1038 } 1039 1040 /** 1041 * @deprecated Use {@link #getLutViewer()} instead 1042 */ 1043 @Deprecated 1044 public IcyLutViewer getLutPanel() 1045 { 1046 return getLutViewer(); 1047 } 1048 1049 /** 1050 * @deprecated Use {@link #setLutViewer(LUTViewer)} instead. 1051 */ 1052 @Deprecated 1053 public void setLutPanel(IcyLutViewer lutViewer) 1054 { 1055 setLutViewer((LUTViewer) lutViewer); 1056 } 1057 1058 /** 1059 * Return the viewer ToolBar object 1060 */ 1061 public JToolBar getToolBar() 1062 { 1063 return toolBar; 1064 } 1065 1066 /** 1067 * @return current T (-1 if all selected/displayed) 1068 */ 1069 public int getPositionT() 1070 { 1071 if (canvas != null) 1072 return canvas.getPositionT(); 1073 1074 return 0; 1075 } 1076 1077 /** 1078 * Set the current T position (for multi frame sequence). 1079 */ 1080 public void setPositionT(int t) 1081 { 1082 if (canvas != null) 1083 canvas.setPositionT(t); 1084 } 1085 1086 /** 1087 * @return current Z (-1 if all selected/displayed) 1088 */ 1089 public int getPositionZ() 1090 { 1091 if (canvas != null) 1092 return canvas.getPositionZ(); 1093 1094 return 0; 1095 } 1096 1097 /** 1098 * Set the current Z position (for stack sequence). 1099 */ 1100 public void setPositionZ(int z) 1101 { 1102 if (canvas != null) 1103 canvas.setPositionZ(z); 1104 } 1105 1106 /** 1107 * @return current C (-1 if all selected/displayed) 1108 */ 1109 public int getPositionC() 1110 { 1111 if (canvas != null) 1112 return canvas.getPositionC(); 1113 1114 return 0; 1115 } 1116 1117 /** 1118 * Set the current C (channel) position (multi channel sequence) 1119 */ 1120 public void setPositionC(int c) 1121 { 1122 if (canvas != null) 1123 canvas.setPositionC(c); 1124 } 1125 1126 /** 1127 * @deprecated Use {@link #getPositionT()} instead. 1128 */ 1129 @Deprecated 1130 public int getT() 1131 { 1132 return getPositionT(); 1133 } 1134 1135 /** 1136 * @deprecated Use {@link #setPositionT(int)} instead. 1137 */ 1138 @Deprecated 1139 public void setT(int t) 1140 { 1141 setPositionT(t); 1142 } 1143 1144 /** 1145 * @deprecated Use {@link #getPositionZ()} instead. 1146 */ 1147 @Deprecated 1148 public int getZ() 1149 { 1150 return getPositionZ(); 1151 } 1152 1153 /** 1154 * @deprecated Use {@link #setPositionZ(int)} instead. 1155 */ 1156 @Deprecated 1157 public void setZ(int z) 1158 { 1159 setPositionZ(z); 1160 } 1161 1162 /** 1163 * @deprecated Use {@link #getPositionZ()} instead. 1164 */ 1165 @Deprecated 1166 public int getC() 1167 { 1168 return getPositionC(); 1169 } 1170 1171 /** 1172 * @deprecated Use {@link #setPositionZ(int)} instead. 1173 */ 1174 @Deprecated 1175 public void setC(int c) 1176 { 1177 setPositionC(c); 1178 } 1179 1180 /** 1181 * Get maximum T value 1182 */ 1183 public int getMaxT() 1184 { 1185 if (canvas != null) 1186 return canvas.getMaxPositionT(); 1187 1188 return 0; 1189 } 1190 1191 /** 1192 * Get maximum Z value 1193 */ 1194 public int getMaxZ() 1195 { 1196 if (canvas != null) 1197 return canvas.getMaxPositionZ(); 1198 1199 return 0; 1200 } 1201 1202 /** 1203 * Get maximum C value 1204 */ 1205 public int getMaxC() 1206 { 1207 if (canvas != null) 1208 return canvas.getMaxPositionC(); 1209 1210 return 0; 1211 } 1212 1213 /** 1214 * return true if current canvas's viewer does support synchronized view 1215 */ 1216 public boolean isSynchronizedViewSupported() 1217 { 1218 if (canvas != null) 1219 return canvas.isSynchronizationSupported(); 1220 1221 return false; 1222 } 1223 1224 /** 1225 * @return the viewSyncId 1226 */ 1227 public int getViewSyncId() 1228 { 1229 if (canvas != null) 1230 return canvas.getSyncId(); 1231 1232 return -1; 1233 } 1234 1235 /** 1236 * Set the view synchronization group id (0 means unsynchronized). 1237 * 1238 * @param id 1239 * the view synchronization id to set 1240 * @see IcyCanvas#setSyncId(int) 1241 */ 1242 public boolean setViewSyncId(int id) 1243 { 1244 if (canvas != null) 1245 return canvas.setSyncId(id); 1246 1247 return false; 1248 } 1249 1250 /** 1251 * Return true if this viewer has its view synchronized 1252 */ 1253 public boolean isViewSynchronized() 1254 { 1255 if (canvas != null) 1256 return canvas.isSynchronized(); 1257 1258 return false; 1259 } 1260 1261 /** 1262 * Delegation for {@link IcyCanvas#getImage(int, int, int)} 1263 */ 1264 public IcyBufferedImage getImage(int t, int z, int c) 1265 { 1266 if (canvas != null) 1267 return canvas.getImage(t, z, c); 1268 1269 return null; 1270 } 1271 1272 /** 1273 * @deprecated Use {@link #getImage(int, int, int)} with C = -1 instead. 1274 */ 1275 @Deprecated 1276 public IcyBufferedImage getImage(int t, int z) 1277 { 1278 return getImage(t, z, -1); 1279 } 1280 1281 /** 1282 * Get the current image 1283 * 1284 * @return current image 1285 */ 1286 public IcyBufferedImage getCurrentImage() 1287 { 1288 if (canvas != null) 1289 return canvas.getCurrentImage(); 1290 1291 return null; 1292 } 1293 1294 /** 1295 * Return the number of "selected" samples 1296 */ 1297 public int getNumSelectedSamples() 1298 { 1299 if (canvas != null) 1300 return canvas.getNumSelectedSamples(); 1301 1302 return 0; 1303 } 1304 1305 /** 1306 * @see icy.canvas.IcyCanvas#getRenderedImage(int, int, int, boolean) 1307 */ 1308 public BufferedImage getRenderedImage(int t, int z, int c, boolean canvasView) 1309 { 1310 if (canvas == null) 1311 return null; 1312 1313 return canvas.getRenderedImage(t, z, c, canvasView); 1314 } 1315 1316 /** 1317 * @see icy.canvas.IcyCanvas#getRenderedSequence(boolean, icy.common.listener.ProgressListener) 1318 */ 1319 public Sequence getRenderedSequence(boolean canvasView, ProgressListener progressListener) 1320 { 1321 if (canvas == null) 1322 return null; 1323 1324 return canvas.getRenderedSequence(canvasView, progressListener); 1325 } 1326 1327 /** 1328 * Returns the T navigation panel. 1329 */ 1330 protected TNavigationPanel getTNavigationPanel() 1331 { 1332 if (canvas == null) 1333 return null; 1334 1335 return canvas.getTNavigationPanel(); 1336 } 1337 1338 /** 1339 * Returns the frame rate (given in frame per second) for play command. 1340 */ 1341 public int getFrameRate() 1342 { 1343 final TNavigationPanel tNav = getTNavigationPanel(); 1344 1345 if (tNav != null) 1346 return tNav.getFrameRate(); 1347 1348 return 0; 1349 } 1350 1351 /** 1352 * Sets the frame rate (given in frame per second) for play command. 1353 */ 1354 public void setFrameRate(int fps) 1355 { 1356 final TNavigationPanel tNav = getTNavigationPanel(); 1357 1358 if (tNav != null) 1359 tNav.setFrameRate(fps); 1360 } 1361 1362 /** 1363 * Returns true if <code>repeat</code> is enabled for play command. 1364 */ 1365 public boolean isRepeat() 1366 { 1367 final TNavigationPanel tNav = getTNavigationPanel(); 1368 1369 if (tNav != null) 1370 return tNav.isRepeat(); 1371 1372 return false; 1373 } 1374 1375 /** 1376 * Set <code>repeat</code> mode for play command. 1377 */ 1378 public void setRepeat(boolean value) 1379 { 1380 final TNavigationPanel tNav = getTNavigationPanel(); 1381 1382 if (tNav != null) 1383 tNav.setRepeat(value); 1384 } 1385 1386 /** 1387 * Returns true if currently playing. 1388 */ 1389 public boolean isPlaying() 1390 { 1391 final TNavigationPanel tNav = getTNavigationPanel(); 1392 1393 if (tNav != null) 1394 return tNav.isPlaying(); 1395 1396 return false; 1397 } 1398 1399 /** 1400 * Start sequence play. 1401 * 1402 * @see #stopPlay() 1403 * @see #setRepeat(boolean) 1404 */ 1405 public void startPlay() 1406 { 1407 final TNavigationPanel tNav = getTNavigationPanel(); 1408 1409 if (tNav != null) 1410 tNav.startPlay(); 1411 } 1412 1413 /** 1414 * Stop sequence play. 1415 * 1416 * @see #startPlay() 1417 */ 1418 public void stopPlay() 1419 { 1420 final TNavigationPanel tNav = getTNavigationPanel(); 1421 1422 if (tNav != null) 1423 tNav.stopPlay(); 1424 } 1425 1426 /** 1427 * Return true if only this viewer is currently displaying its attached sequence 1428 */ 1429 public boolean isUnique() 1430 { 1431 return Icy.getMainInterface().isUniqueViewer(this); 1432 } 1433 1434 protected void lutChanged() 1435 { 1436 // can be called from external thread, replace it in AWT dispatch thread 1437 ThreadUtil.bgRunSingle(lutUpdater); 1438 } 1439 1440 protected void positionChanged(DimensionId dim) 1441 { 1442 fireViewerChanged(ViewerEventType.POSITION_CHANGED, dim); 1443 } 1444 1445 /** 1446 * Add a listener 1447 * 1448 * @param listener 1449 */ 1450 public void addListener(ViewerListener listener) 1451 { 1452 listeners.add(ViewerListener.class, listener); 1453 } 1454 1455 /** 1456 * Remove a listener 1457 * 1458 * @param listener 1459 */ 1460 public void removeListener(ViewerListener listener) 1461 { 1462 listeners.remove(ViewerListener.class, listener); 1463 } 1464 1465 void fireViewerChanged(ViewerEventType eventType, DimensionId dim) 1466 { 1467 final ViewerEvent event = new ViewerEvent(this, eventType, dim); 1468 1469 for (ViewerListener viewerListener : listeners.getListeners(ViewerListener.class)) 1470 viewerListener.viewerChanged(event); 1471 } 1472 1473 void fireViewerChanged(ViewerEventType event) 1474 { 1475 fireViewerChanged(event, DimensionId.NULL); 1476 } 1477 1478 protected void fireViewerClosed() 1479 { 1480 for (ViewerListener viewerListener : listeners.getListeners(ViewerListener.class)) 1481 viewerListener.viewerClosed(this); 1482 } 1483 1484 @Override 1485 public void keyPressed(KeyEvent e) 1486 { 1487 // forward to canvas 1488 if ((canvas != null) && (!e.isConsumed())) 1489 canvas.keyPressed(e); 1490 } 1491 1492 @Override 1493 public void keyReleased(KeyEvent e) 1494 { 1495 // forward to canvas 1496 if ((canvas != null) && (!e.isConsumed())) 1497 canvas.keyReleased(e); 1498 } 1499 1500 @Override 1501 public void keyTyped(KeyEvent e) 1502 { 1503 // forward to canvas 1504 if ((canvas != null) && (!e.isConsumed())) 1505 canvas.keyTyped(e); 1506 } 1507 1508 /** 1509 * Change the frame's title. 1510 */ 1511 protected void refreshViewerTitle() 1512 { 1513 // have to test this as we release sequence reference on closed 1514 if (sequence != null) 1515 setTitle(sequence.getName()); 1516 } 1517 1518 @Override 1519 public void sequenceChanged(SequenceEvent event) 1520 { 1521 switch (event.getSourceType()) 1522 { 1523 case SEQUENCE_META: 1524 final String meta = (String) event.getSource(); 1525 1526 if (StringUtil.isEmpty(meta) || StringUtil.equals(meta, Sequence.ID_NAME)) 1527 refreshViewerTitle(); 1528 // update virtual state if needed 1529 if (initialized && StringUtil.equals(meta, Sequence.ID_VIRTUAL)) 1530 refreshToolBar(); 1531 break; 1532 1533 case SEQUENCE_DATA: 1534 break; 1535 1536 case SEQUENCE_TYPE: 1537 // might need initialization 1538 if (!initialized && (sequence != null) && !sequence.isEmpty()) 1539 { 1540 adjustViewerToImageSize(); 1541 initialized = true; 1542 } 1543 1544 // we update LUT on type change directly on getLut() method 1545 1546 // // try to keep current LUT if possible 1547 if (!sequence.isLutCompatible(lut)) 1548 // need to update the lut according to the colormodel change 1549 setLut(sequence.createCompatibleLUT()); 1550 break; 1551 1552 case SEQUENCE_COLORMAP: 1553 1554 break; 1555 1556 case SEQUENCE_COMPONENTBOUNDS: 1557 // refresh lut scalers from sequence default lut 1558 final LUT sequenceLut = sequence.getDefaultLUT(); 1559 1560 if (!sequenceLut.isCompatible(lut) || (lutViewer == null)) 1561 lut.setScalers(sequenceLut); 1562 break; 1563 } 1564 } 1565 1566 @Override 1567 public void sequenceClosed(Sequence sequence) 1568 { 1569 1570 } 1571 1572 @Override 1573 public void canvasChanged(IcyCanvasEvent event) 1574 { 1575 switch (event.getType()) 1576 { 1577 case POSITION_CHANGED: 1578 // common process on position change 1579 positionChanged(event.getDim()); 1580 break; 1581 1582 case SYNC_CHANGED: 1583 ThreadUtil.invokeLater(new Runnable() 1584 { 1585 @Override 1586 public void run() 1587 { 1588 refreshLockCombo(); 1589 } 1590 }); 1591 break; 1592 } 1593 } 1594 1595 @Override 1596 public void propertyChange(PropertyChangeEvent evt) 1597 { 1598 super.propertyChange(evt); 1599 1600 // Canvas property "layer visible" changed ? 1601 if (StringUtil.equals(evt.getPropertyName(), IcyCanvas.PROPERTY_LAYERS_VISIBLE)) 1602 { 1603 refreshToolBar(); 1604 updateSystemMenu(); 1605 } 1606 } 1607 1608 @Override 1609 public void pluginLoaderChanged(PluginLoaderEvent e) 1610 { 1611 ThreadUtil.invokeLater(new Runnable() 1612 { 1613 @Override 1614 public void run() 1615 { 1616 // refresh available canvas 1617 buildCanvasCombo(); 1618 // and refresh components 1619 refreshCanvasCombo(); 1620 updateToolbarComponents(); 1621 } 1622 }); 1623 } 1624}