/*========================================================================= Program: FusionViewer Module: $RCSfile: ImageViewFrame.java,v $ Language: Java Date: $Date: 2008/01/11 21:43:26 $ Version: $Revision: 1.6 $ Copyright (c) Insightful Corporation. All rights reserved. See Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ package org.fusionviewer.ui; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.ComponentListener; import java.awt.event.ComponentEvent; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.JToolBar; import javax.swing.JButton; import javax.swing.Action; import javax.swing.AbstractAction; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.ActionMap; import javax.swing.JCheckBoxMenuItem; import org.fusionviewer.display.Image; import org.fusionviewer.display.ImageSliceView; import org.fusionviewer.display.SliceConfiguration; import org.fusionviewer.io.Env; import org.fusionviewer.model.FusionDisplayModel; import org.fusionviewer.model.ImageDisplayModel; import org.fusionviewer.model.LinkedCursorDisplayModel; import org.fusionviewer.ui.OpenImagesDialog; import com.sun.SwingWorker; import javax.media.opengl.DefaultGLCapabilitiesChooser; import javax.media.opengl.GLCanvas; import javax.media.opengl.GLCapabilities; /**\class ImageViewFrame *\brief Display window containing 1, 3, or 6 image panels and a control panel. */ public class ImageViewFrame extends JFrame implements ComponentListener { private static final int MAX_NUM_IMAGES = 2; //image volume number, PET and CT private static final int MIN_WIDTH = 1200; private static final int MIN_HEIGHT = 800; public final static int DISPLAY_FUSION = 1; public final static int DISPLAY_LINKEDCURSOR = 2; public final static int DISPLAY_2D = 3; private GLCanvas[] m_canvas; // heavyweight AWT view components private ImageSliceView[] m_view; // objects that handle drawing for the GLCanvases private ImageDisplayModel m_viewModel; // Display model which contains view parameters private Image[] m_images = new Image[MAX_NUM_IMAGES]; //image array list private int m_refImageId; // reference image Id (0:BackgroundImage 1:ForegroundImage) protected int m_numImages; // number of image volume displayed (1 or 2) private JButton[] m_toolButtons; // tool palette buttons private int m_layout; // Current layout: fusion or linked cursor defined // in OpenImagesDialog private int m_imageOrientation; // Image orientation as defined in ViewArrangement private JMenu m_viewMenu; // menu for view commands private Component m_controlPane; // container for viewer controls public ImageViewFrame(){ super(); // GLCanvas is a heavyweight component. Hence, Swing menus can potentially be hidden. // To avoid this, we need to make the menus heavyweight as well. See: // http://java.sun.com/products/jfc/tsc/articles/mixing/ javax.swing.JPopupMenu.setDefaultLightWeightPopupEnabled(false); addComponentListener(this); maximizeWindow(this); setTitle("FusionViewer 1.0 (Beta)"); setJMenuBar(createMenuBar(true)); m_viewMenu.setEnabled(false); setVisible(true); doFileOpen(); } /* * Create the main viewer panel to show MPR views in either fusion or linked * cursor layouts */ private JPanel createPanel(boolean fusionLayout){ JPanel mainPanel = new JPanel(); setJMenuBar(createMenuBar(true)); // Regardless of the layout, use a FusionDisplayModel (it's derived from LinkedCursorDisplayModel) // so that the user can switch between modes and keep all display settings FusionDisplayModel viewModel; if (m_viewModel == null) { viewModel = new FusionDisplayModel(MAX_NUM_IMAGES, fusionLayout, m_refImageId); m_viewModel = viewModel; for (int i = 0; i < m_numImages; i++){ viewModel.setImage(i, m_images[i], fusionLayout, false); } } else { viewModel = (FusionDisplayModel) m_viewModel; } SliceConfiguration sliceConfig[] = new SliceConfiguration[3]; switch (m_imageOrientation) { case ViewArrangement.AXIAL: sliceConfig[0] = new SliceConfiguration(SliceConfiguration.AXIAL); sliceConfig[1] = new SliceConfiguration(SliceConfiguration.CORONAL); sliceConfig[2] = new SliceConfiguration(SliceConfiguration.SAGITTAL); break; case ViewArrangement.SAGITTAL: sliceConfig[0] = new SliceConfiguration(SliceConfiguration.AXIAL); sliceConfig[1] = new SliceConfiguration(2, 0, 1, false, false); sliceConfig[2] = new SliceConfiguration(2, 1, 0, false, false); break; case ViewArrangement.CORONAL: sliceConfig[0] = new SliceConfiguration(0, 1, 2, false, false); sliceConfig[1] = new SliceConfiguration(2, 1, 0, false, false); sliceConfig[2] = new SliceConfiguration(0, 2, 1, false, true); break; } if (fusionLayout) { m_canvas = new GLCanvas[3]; m_view = new ImageSliceView[3]; mainPanel.setLayout(new GridLayout(2, 2)); mainPanel.add(createImagePanel(0, sliceConfig[0])); mainPanel.add(createImagePanel(1, sliceConfig[1])); mainPanel.add(createImagePanel(2, sliceConfig[2])); m_controlPane = createFusionControls(viewModel, true); mainPanel.add(m_controlPane); for (int i = 0; i < 3; i++) { m_view[i].setModel(m_viewModel); for (int j = 0; j < m_numImages; j++) m_view[i].addImage(j); } } else { int numViews = 3 * m_numImages; m_canvas = new GLCanvas[numViews]; m_view = new ImageSliceView[numViews]; mainPanel.setLayout(new BorderLayout()); JPanel imagePanel = new JPanel(); imagePanel.setLayout(new GridLayout(m_numImages, 3)); imagePanel.add(createImagePanel(0, sliceConfig[0])); imagePanel.add(createImagePanel(1, sliceConfig[1])); imagePanel.add(createImagePanel(2, sliceConfig[2])); for (int i = 0; i < 3; i++) { m_view[i].setModel(m_viewModel); m_view[i].addImage(0); } if (m_numImages > 1) { imagePanel.add(createImagePanel(3, sliceConfig[0])); imagePanel.add(createImagePanel(4, sliceConfig[1])); imagePanel.add(createImagePanel(5, sliceConfig[2])); for (int i = 3; i < 6; i++) { m_view[i].setModel(m_viewModel); m_view[i].addImage(1); } } mainPanel.add(imagePanel); m_controlPane = createLinkedCursorControls(viewModel); mainPanel.add(m_controlPane, BorderLayout.SOUTH); } mainPanel.setOpaque(true); return mainPanel; } /* * Create the main panel to show a 2D image instead of a 3D image as MPR views */ private JPanel create2DPanel(){ setJMenuBar(createMenuBar(false)); JPanel mainPanel = new JPanel(); mainPanel.setOpaque(true); m_canvas = new GLCanvas[1]; m_view = new ImageSliceView[1]; FusionDisplayModel viewModel = new FusionDisplayModel(MAX_NUM_IMAGES, true, m_refImageId); m_viewModel = viewModel; m_viewModel.setCrosshairsVisible(false); for (int i = 0; i < m_numImages; i++) viewModel.setImage(i, m_images[i],true,false); mainPanel.setLayout(new BorderLayout()); mainPanel.add(createImagePanel(0, new SliceConfiguration(0, 1, 2, false, false))); m_controlPane = createFusionControls(viewModel, false); mainPanel.add(m_controlPane, BorderLayout.SOUTH); m_view[0].setModel(m_viewModel); for (int i = 0; i < m_numImages; i++) m_view[0].addImage(i); return mainPanel; } /* * Create image viewing controls for fusion mode */ private Component createFusionControls(FusionDisplayModel viewModel, boolean showCoordinates) { JPanel panel = new JPanel(); panel.setLayout(new GridBagLayout()); panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); GridBagConstraints constraints = new GridBagConstraints(); constraints.insets = new java.awt.Insets(0, 5, 5, 5); constraints.gridx = 0; constraints.gridy = 0; constraints.weightx = 0.0; constraints.weighty = 0.0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.anchor = GridBagConstraints.CENTER; constraints.fill = GridBagConstraints.NONE; panel.add(createToolBar(showCoordinates, showCoordinates), constraints); constraints.anchor = GridBagConstraints.FIRST_LINE_START; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.gridx++; String[] labels = (m_numImages == 2) ? new String[] {"Background", "Foreground"} : new String[] {""}; panel.add(new ColormapControlPanel(viewModel, labels), constraints); constraints.insets = new java.awt.Insets(5, 5, 5, 5); constraints.gridx = 0; constraints.gridwidth = 2; if (m_numImages > 1) { constraints.gridy++; panel.add(new FusionControlPanel(viewModel), constraints); } if (showCoordinates) { constraints.gridy++; panel.add(new CoordinatesPanel(viewModel, labels), constraints); } constraints.gridy++; PixelInfoPanel infoPanel = new PixelInfoPanel(labels); attachPixelInfoToViews(infoPanel); panel.add(infoPanel, constraints); // Add filler so that the controls are aligned to the left and top instead // of in the center constraints.fill = GridBagConstraints.BOTH; constraints.insets = new java.awt.Insets(0, 0, 0, 0); constraints.gridheight = 1; constraints.gridx = 0; constraints.gridy++; constraints.weightx = 1.0; constraints.weighty = 1.0; panel.add(new JPanel(), constraints); constraints.fill = GridBagConstraints.BOTH; constraints.gridheight = constraints.gridy + 1; constraints.gridx = 2; constraints.gridy = 0; panel.add(new JPanel(), constraints); return panel; } /* * Create image viewing controls for linked cursor mode */ private Component createLinkedCursorControls(LinkedCursorDisplayModel viewModel) { JPanel panel = new JPanel(); panel.setLayout(new GridBagLayout()); panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.FIRST_LINE_START; constraints.fill = GridBagConstraints.NONE; constraints.insets = new java.awt.Insets(5, 5, 5, 5); constraints.gridx = 1; constraints.gridy = 0; constraints.gridheight = 2; constraints.weighty = 1.0; panel.add(createToolBar(true, false), constraints); constraints.gridx++; String[] labels = (m_numImages == 2) ? new String[] {"Top", "Bottom"} : new String[] {""}; panel.add(new ColormapControlPanel(viewModel, labels), constraints); constraints.gridx++; constraints.gridheight = 1; constraints.fill = GridBagConstraints.NONE; panel.add(new CoordinatesPanel(viewModel, labels), constraints); constraints.gridy = 1; constraints.fill = GridBagConstraints.HORIZONTAL; PixelInfoPanel infoPanel = new PixelInfoPanel(labels); attachPixelInfoToViews(infoPanel); panel.add(infoPanel, constraints); // Add fillers to take up remaining space so that the above controls are not // enlarged to beyond their preferred sizes constraints.fill = GridBagConstraints.BOTH; constraints.gridheight = 2; constraints.gridx++; constraints.weightx = 1.0; panel.add(new JPanel(), constraints); constraints.gridx = 0; panel.add(new JPanel(), constraints); return panel; } /* * Add each image view as a listener to a PixelInfoPanel */ private void attachPixelInfoToViews(PixelInfoPanel infoPanel) { for (int i = 0; i < m_view.length; i++) { m_view[i].addPixelInfoListener(infoPanel); } } /* * Create an image view panel */ private Component createImagePanel(int idx, SliceConfiguration mode) { GLCapabilities glCaps = new GLCapabilities(); glCaps.setAlphaBits(8); // The default chooser sometimes will not give us a GLCapabilities // with the alpha we request. Substitute our own chooser here to // ensure we get at least 8 bits of alpha and double buffering. class AlphaChooser extends DefaultGLCapabilitiesChooser { public int chooseCapabilities(GLCapabilities desired, GLCapabilities[] available, int x) { ArrayList filteredList = new ArrayList(); ArrayList reverseIdx = new ArrayList(); for (int i = 0; i < available.length; i++) { if ((available[i] != null) && (available[i].getAlphaBits() >= desired.getAlphaBits())) { filteredList.add(available[i]); reverseIdx.add(new Integer(i)); } } // If we did not find any matching capabilities, just drop back to the // default chooser if (filteredList.size() == 0) { return super.chooseCapabilities(desired, available, x); } else { // Transfer ArrayList to an array of GLCapabilities GLCapabilities[] filteredCaps = new GLCapabilities[filteredList.size()]; for (int i = 0; i < filteredList.size(); i++) filteredCaps[i] = (GLCapabilities) filteredList.get(i); // Call the default chooser with only the matching capabilities list int chosen = super.chooseCapabilities(desired, filteredCaps, x); // From the result index, get the original index of the chosen capability return ((Integer) reverseIdx.get(chosen)).intValue(); } } } AlphaChooser chooser = new AlphaChooser(); m_canvas[idx] = new GLCanvas(glCaps, chooser, null, null); m_view[idx] = new ImageSliceView(m_canvas[idx], mode); m_canvas[idx].addGLEventListener(m_view[idx]); return m_canvas[idx]; } /* * Release views, clean up OpenGL view resources. This method MUST * be called on the Swing thread. */ private void releaseViews() { Container contentPane = getContentPane(); if (m_view != null) { for (int i = 0; i < m_view.length; i++) { m_canvas[i].removeGLEventListener(m_view[i]); m_view[i].release(); contentPane.remove(m_canvas[i]); } } if (m_controlPane != null) contentPane.remove(m_controlPane); m_controlPane = null; m_canvas = null; m_view = null; } /** * Load an image into one of the available image slots * @param dicom */ public void loadImage(File file, int idx, boolean dicom) { if (m_images[idx] != null) { m_images[idx].dispose(); m_images[idx] = null; } if (dicom == false){ if (file == null) return; if (!file.exists()) { JOptionPane.showMessageDialog(this, "File not found.\n\n" + file.getAbsolutePath(), "Error", JOptionPane.ERROR_MESSAGE); return; } if (file.isDirectory()) { File[] files = file.listFiles(); LinkedList filePaths = new LinkedList(); for (int i = 0; i < files.length; i++) { if (!files[i].getName().startsWith(".")) filePaths.add(files[i].getAbsolutePath()); } String[] fileStrings = new String[filePaths.size()]; Iterator it = filePaths.iterator(); int i = 0; while (it.hasNext()) fileStrings[i++] = (String) it.next(); m_images[idx] = new Image(fileStrings,dicom); } else { m_images[idx] = new Image(file.getAbsolutePath(),dicom); } }else { m_images[idx] = new Image(file.getAbsolutePath(),dicom); } } /* * Unload all images */ private void unloadImages() { releaseViews(); for (int i = 0; i < m_images.length; i++) if (m_images[i] != null) { m_images[i].dispose(); m_images[i] = null; } m_viewModel = null; } /* * Show a dialog box to get paths to 2 image files to open. * Show a status display while loading the files. */ protected void doFileOpen() { final OpenImagesDialog dialog = new OpenImagesDialog(this); final StatusDisplay status = new StatusDisplay(); if (dialog.run()) { // Resizing can cause problems with the OpenGL contexts if // this occurs while they are being added to the layout setResizable(false); unloadImages(); setContentPane(status); // Image loading can be lengthy, so we would like to display some // status information. We need to move the image loading to another // thread, however, to allow the status display to draw. SwingWorker imageLoader = new SwingWorker() { /* * Load the foreground and background images */ public Object construct() { int imageIdx = 0; File backgroundFile = dialog.getBackgroundFile(); if (backgroundFile != null) { updateStatus("Opening " + backgroundFile.getName(), 0); loadImage(backgroundFile, imageIdx, dialog.isOpenDICOM()); imageIdx++; } File foregroundFile = dialog.getForegroundFile(); if (foregroundFile != null) { updateStatus("Opening " + foregroundFile.getName(), 50); loadImage(foregroundFile, imageIdx, dialog.isOpenDICOM()); imageIdx++; } m_numImages = imageIdx; return null; } /* * finished is run on the event thread after construct has run, * so we can update the UI from here. */ public void finished() { try { Image img = null; float imgSize; float maxSize = 0; //set the reference image ID as the one with the maximum physical size (usually PET). for (int i = 0; i < m_images.length; i++){ img = m_images[i]; if (img != null) imgSize = img.getDimension(0) * img.getPixelSpacing(0); else break; if (maxSize < imgSize){ maxSize = imgSize; m_refImageId = i; } } img = m_images[m_refImageId]; //reference image if (img != null) { int[] dimensions = img.getDimensions(); if ((dimensions.length == 3) && (dimensions[2] <= 1)) { // 2D image m_layout = DISPLAY_2D; m_imageOrientation = ViewArrangement.AXIAL; setContentPane(create2DPanel()); validate(); // Lay out subcomponents equalizeViewZoom(false); m_viewModel.setPosition(0, new float[] { 0.0f, 0.0f, 0.0f }, true); } else { //3D volume float[] position = new float[dimensions.length]; for (int i = 0; i < dimensions.length; i++) position[i] = dimensions[i] * img.getPixelSpacing(i) / 2.0f; m_layout = dialog.getDisplayMode(); m_imageOrientation = dialog.getImageOrientation(); setContentPane(createPanel(m_layout == DISPLAY_FUSION)); validate(); // Lay out subcomponents equalizeViewZoom(false); m_viewModel.setPosition(0, position, true); } // Get the focus so that our keyboard short-cuts work without the mouse first // needing to be clicked anywhere in the window getRootPane().requestFocus(); m_viewMenu.setEnabled(true); } else { setContentPane(new JPanel()); // Remove status display unloadImages(); } // Restore user ability to resize setResizable(true); } catch (Exception e) { JOptionPane.showMessageDialog(ImageViewFrame.this, "Error while loading file.\n\n" + e.getLocalizedMessage(), "Error", JOptionPane.ERROR_MESSAGE); } } /* * Update the text of the status display and the progress indicator */ private void updateStatus(final String message, final int progress) { SwingUtilities.invokeLater(new Runnable() { public void run() { status.setStatus(message, progress); } }); } }; // Start the image loading worker thread imageLoader.start(); } } /* * Show a dialog box to get paths to 2 DICOM Series to open. * Show a status display while loading the files. */ /* * Zoom all views to the same level (i.e., make pixel spacing the same in all views) */ private void equalizeViewZoom(boolean redraw) { // Find the maximum pixel spacing (and therefore the minimum zoom) when all views are zoomed // to exactly fit their images float xSpacing = 0.0f, ySpacing = 0.0f; for (int i = 0; i < m_view.length; i++) { ImageSliceView view = m_view[i]; GLCanvas canvas = m_canvas[i]; view.zoomToFit(canvas.getWidth(), canvas.getHeight()); if (view.getPixelSpacing(0) > xSpacing) xSpacing = view.getPixelSpacing(0); if (view.getPixelSpacing(1) > ySpacing) ySpacing = view.getPixelSpacing(1); } // Set the zoom for all views to the minimum zoom found if ((xSpacing != 0.0f) && (ySpacing != 0.0f)) { for(int i = 0; i < m_view.length; i++) { m_view[i].setSpacing(xSpacing, ySpacing,redraw); } } } /* * Create a menubar for this frame */ private JMenuBar createMenuBar(boolean viewEnabled){ JMenuBar menuBar = new JMenuBar(); int shortcutKeyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); // Create the File menu JMenu fileMenu = new JMenu("File"); fileMenu.setMnemonic(KeyEvent.VK_F); JMenuItem openItem = fileMenu.add("Open"); PlatformUtility.setMnemonic(openItem, KeyEvent.VK_O); openItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, shortcutKeyMask)); openItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { doFileOpen(); } }); if (!Env.isMac) { fileMenu.addSeparator(); JMenuItem exitItem = fileMenu.add("Exit"); PlatformUtility.setMnemonic(exitItem, KeyEvent.VK_X); exitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, shortcutKeyMask)); exitItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { System.exit(0); } }); } // Create the View menu JMenu viewMenu = new JMenu("View"); viewMenu.setMnemonic(KeyEvent.VK_V); JCheckBoxMenuItem crosshairItem = new JCheckBoxMenuItem("Show Crosshairs"); viewMenu.add(crosshairItem); PlatformUtility.setMnemonic(crosshairItem, KeyEvent.VK_S); crosshairItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { if (evt.getSource() instanceof JMenuItem) { JMenuItem item = (JMenuItem) evt.getSource(); boolean crosshairsVisible = !m_viewModel.getCrosshairsVisible(); m_viewModel.setCrosshairsVisible(crosshairsVisible); item.setSelected(crosshairsVisible); } } }); if (viewEnabled) { if (m_viewModel != null) crosshairItem.setSelected(m_viewModel.getCrosshairsVisible()); else crosshairItem.setSelected(true); } JMenuItem sizeItem = viewMenu.add("Switch Layout"); PlatformUtility.setMnemonic(sizeItem, KeyEvent.VK_D); sizeItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { if (m_layout == DISPLAY_FUSION) { m_layout = DISPLAY_LINKEDCURSOR; m_viewModel.setInverted(m_refImageId, true); m_viewModel.setColormap(m_refImageId, m_viewModel.getColormapArray().getColormap(0)); } else if (m_layout == DISPLAY_LINKEDCURSOR) { m_layout = DISPLAY_FUSION; m_viewModel.setInverted(m_refImageId, false); m_viewModel.setColormap(m_refImageId, m_viewModel.getColormapArray().getColormap(1)); }else return; try { releaseViews(); setContentPane(createPanel(m_layout == ImageViewFrame.DISPLAY_FUSION)); componentResized(null); // ensure minimum window size for the new controls validate(); // Lay out subcomponents equalizeViewZoom(false); } catch (Exception e) { JOptionPane.showMessageDialog(ImageViewFrame.this, "Error while switching layouts.\n\n" + e.getLocalizedMessage(), "Error", JOptionPane.ERROR_MESSAGE); unloadImages(); doFileOpen(); } } }); //create resize options JMenu resizeMenu = new JMenu("Resize"); resizeMenu.setMnemonic(KeyEvent.VK_S); JMenuItem resetZoomItem = resizeMenu.add("Reset Zoom"); PlatformUtility.setMnemonic(resetZoomItem, KeyEvent.VK_R); resetZoomItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { releaseViews(); setContentPane(createPanel(m_layout == ImageViewFrame.DISPLAY_FUSION)); componentResized(null); // ensure minimum window size for the new controls validate(); // Lay out subcomponents equalizeViewZoom(true); } }); if (!viewEnabled) viewMenu.setEnabled(false); else m_viewMenu = viewMenu; menuBar.add(fileMenu); menuBar.add(viewMenu); menuBar.add(resizeMenu); return menuBar; } /* * Create a toolbar for exploring the image in this frame */ private Component createToolBar(boolean includeNavigator, boolean wraps) { JToolBar toolbar = new JToolBar("Tool", includeNavigator ? JToolBar.VERTICAL : JToolBar.HORIZONTAL); toolbar.setFloatable(false); toolbar.setRollover(false); if (wraps) toolbar.setLayout(new GridLayout(2, 3)); m_toolButtons = new JButton[6]; Action[] toolActions = new Action[6]; class ToolButtonAction extends AbstractAction { private int m_tool; public ToolButtonAction(String name, int tool) { super(name); m_tool = tool; } public void actionPerformed(ActionEvent e) { switchTool(m_tool); } }; toolActions[0] = new ToolButtonAction("Navigate", ImageDisplayModel.MOUSE_MODE_NAVIGATE); toolActions[1] = new ToolButtonAction("Zoom", ImageDisplayModel.MOUSE_MODE_ZOOM); toolActions[2] = new ToolButtonAction("Pan", ImageDisplayModel.MOUSE_MODE_PAN); toolActions[3] = new ToolButtonAction("Snapshot", ImageDisplayModel.MOUSE_MODE_COPYIMAGE); toolActions[4] = new ToolButtonAction("ROI", ImageDisplayModel.MOUSE_MODE_RECTANGLE_ROI); toolActions[5] = new ToolButtonAction("Measure Line", ImageDisplayModel.MOUSE_MODE_MEASURE_LINE); m_toolButtons[0] = new JButton(PlatformUtility.createImageIcon("images/crosshair.png", "Navigate")); m_toolButtons[0].setToolTipText("Navigate"); m_toolButtons[0].setRolloverEnabled(true); m_toolButtons[0].addActionListener(toolActions[0]); m_toolButtons[1] = new JButton(PlatformUtility.createImageIcon("images/magnify.png", "Zoom")); m_toolButtons[1].setToolTipText("Zoom"); m_toolButtons[1].setRolloverEnabled(true); m_toolButtons[1].addActionListener(toolActions[1]); m_toolButtons[2] = new JButton(PlatformUtility.createImageIcon("images/open-hand.png", "Pan")); m_toolButtons[2].setToolTipText("Pan"); m_toolButtons[2].setRolloverEnabled(true); m_toolButtons[2].addActionListener(toolActions[2]); m_toolButtons[3] = new JButton(PlatformUtility.createImageIcon("images/camera.png", "Snapshot")); m_toolButtons[3].setToolTipText("Snapshot"); m_toolButtons[3].setRolloverEnabled(true); m_toolButtons[3].addActionListener(toolActions[3]); m_toolButtons[4] = new JButton(PlatformUtility.createImageIcon("images/roi.png", "ROI")); m_toolButtons[4].setToolTipText("ROI"); m_toolButtons[4].setRolloverEnabled(true); m_toolButtons[4].addActionListener(toolActions[4]); m_toolButtons[5] = new JButton(PlatformUtility.createImageIcon("images/measure-line.png", "Measure Line")); m_toolButtons[5].setToolTipText("Measure Line"); m_toolButtons[5].setRolloverEnabled(true); m_toolButtons[5].addActionListener(toolActions[5]); if (includeNavigator) { m_viewModel.setMouseMode(ImageDisplayModel.MOUSE_MODE_NAVIGATE); for (int i = 0; i < m_toolButtons.length; i++) toolbar.add(m_toolButtons[i]); m_toolButtons[0].setSelected(true); } else { m_viewModel.setMouseMode(ImageDisplayModel.MOUSE_MODE_ZOOM); for (int i = 1; i < m_toolButtons.length; i++) toolbar.add(m_toolButtons[i]); m_toolButtons[1].setSelected(true); } // Create keyboard short-cuts for selecting tools InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); ActionMap actionMap = getRootPane().getActionMap(); if (includeNavigator) { int shortcutKeyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); String[] toolActionStrings = new String[] { "Tool.Navigate", "Tool.Zoom", "Tool.Pan", "Tool.Snapshot", "Tool.ROI", "Tool.MeasureLine" }; inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_1, shortcutKeyMask), toolActionStrings[0]); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_2, shortcutKeyMask), toolActionStrings[1]); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_3, shortcutKeyMask), toolActionStrings[2]); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_4, shortcutKeyMask), toolActionStrings[3]); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_5, shortcutKeyMask), toolActionStrings[4]); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_6, shortcutKeyMask), toolActionStrings[5]); for (int i = 0; i < toolActionStrings.length; i++) actionMap.put(toolActionStrings[i], toolActions[i]); } else { int shortcutKeyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); String[] toolActionStrings = new String[] { "Tool.Zoom", "Tool.Pan", "Tool.Snapshot", "Tool.ROI", "Tool.MeasureLine" }; inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_1, shortcutKeyMask), toolActionStrings[0]); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_2, shortcutKeyMask), toolActionStrings[1]); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_3, shortcutKeyMask), toolActionStrings[2]); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_4, shortcutKeyMask), toolActionStrings[3]); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_5, shortcutKeyMask), toolActionStrings[4]); for (int i = 0; i < toolActionStrings.length; i++) actionMap.put(toolActionStrings[i], toolActions[i + 1]); } return toolbar; } /* * Change the active tool */ private void switchTool(int tool) { int prevTool = m_viewModel.getMouseMode(); if (tool == prevTool) return; m_toolButtons[prevTool - 1].setSelected(false); m_toolButtons[tool - 1].setSelected(true); m_viewModel.setMouseMode(tool); } /* * Maximize the main window to the largest square window that will fit on the main display */ private void maximizeWindow(Window w) { Toolkit toolkit = Toolkit.getDefaultToolkit(); GraphicsDevice mainDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); // Get dimensions of the main screen and insets to account for widgets like the Windows taskbar Dimension screenDim = toolkit.getScreenSize(); Insets insets = toolkit.getScreenInsets(mainDevice.getDefaultConfiguration()); // Calculate the largest square window that can fit on the display int size = Math.min(screenDim.width - (insets.left + insets.right), screenDim.height - (insets.top + insets.bottom)); w.setSize(size, size); w.setLocation(insets.left, insets.top); } /* * Respond to frame resize events by making sure the frame is not too small */ public void componentResized(ComponentEvent e) { int width = getWidth(); int height = getHeight(); int minWidth = MIN_WIDTH; int minHeight = MIN_HEIGHT; if (m_controlPane != null) { Dimension size = m_controlPane.getMinimumSize(); minWidth = size.width; minHeight = size.height; switch (m_layout) { case DISPLAY_FUSION: minWidth *= 2; minHeight *= 2; break; case DISPLAY_LINKEDCURSOR: minHeight *= 3; break; case DISPLAY_2D: minHeight *= 2; break; } } boolean resize = false; if (width < minWidth) { resize = true; width = minWidth; } if (height < minHeight) { resize = true; height = minHeight; } if (resize) setSize(width, height); } public void componentMoved(ComponentEvent e) { } public void componentShown(ComponentEvent e) { } public void componentHidden(ComponentEvent e) { } /* * Helper class to display task progress. Creates a fixed-size box * centered in its container. Contains a one-line text message * followed by a progress bar. */ private class StatusDisplay extends JPanel { private JProgressBar m_progress; private JLabel m_note; public StatusDisplay() { Box panel = Box.createVerticalBox(); panel.add(Box.createVerticalGlue()); m_note = new JLabel("Loading..."); panel.add(m_note); panel.add(Box.createVerticalStrut(10)); m_progress = new JProgressBar(); m_progress.setIndeterminate(true); panel.add(m_progress); panel.add(Box.createVerticalGlue()); panel.setPreferredSize(new Dimension(300, 80)); panel.setMaximumSize(getPreferredSize()); panel.setMinimumSize(getPreferredSize()); // To center the fixed-size progress display, we need to place // it in a 3x3 GridBag with the border cells set to fill as much // space as possible. setLayout(new GridBagLayout()); GridBagConstraints fillerConstraints = new GridBagConstraints(); fillerConstraints.fill = GridBagConstraints.BOTH; fillerConstraints.weightx = 1.0; fillerConstraints.weighty = 1.0; // Create top fillerConstraints.gridx = 0; fillerConstraints.gridy = 0; fillerConstraints.gridwidth = 3; add(new JPanel(), fillerConstraints); // Bottom fillerConstraints.gridy = 2; add(new JPanel(), fillerConstraints); // Left fillerConstraints.gridy = 1; fillerConstraints.gridwidth = 1; add(new JPanel(), fillerConstraints); // Right fillerConstraints.gridx = 2; add(new JPanel(), fillerConstraints); // Center fillerConstraints.gridx = 1; fillerConstraints.gridy = 1; fillerConstraints.fill = GridBagConstraints.NONE; fillerConstraints.weightx = 0.0; fillerConstraints.weighty = 0.0; setOpaque(true); add(panel, fillerConstraints); } /** * @param message * @param progress */ public synchronized void setStatus(String message, int progress) { m_progress.setValue(progress); m_note.setText(message); } } }