package gdsc.utils; /*----------------------------------------------------------------------------- * GDSC Plugins for ImageJ * * Copyright (C) 2011 Alex Herbert * Genome Damage and Stability Centre * University of Sussex, UK * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. *---------------------------------------------------------------------------*/ import ij.IJ; import ij.ImageListener; import ij.ImagePlus; import ij.Prefs; import ij.WindowManager; import ij.gui.GUI; import ij.macro.MacroRunner; import ij.plugin.frame.PlugInFrame; import java.awt.BorderLayout; import java.awt.Choice; import java.awt.Component; import java.awt.FlowLayout; import java.awt.Frame; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Label; import java.awt.Panel; import java.awt.Point; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.List; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JToggleButton; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import gdsc.UsageTracker; /** * Provides the ability to synchronise the display frame of multiple stack windows */ public class Stack_Synchroniser extends PlugInFrame implements ItemListener, ImageListener, ListSelectionListener { private static final long serialVersionUID = 1L; private static String TITLE = "Stack Synchroniser"; private static Frame instance; private ImagePlus parentImage = null; private int currentSlice = 0; private List<ImagePlus> childImages = new ArrayList<ImagePlus>(); // Options private final String OPT_LOCATION = "Stack_Synchroniser.location"; private Choice imageChoice; private JToggleButton synchroniseButton; private JButton helpButton; @SuppressWarnings("rawtypes") private DefaultListModel listModel; @SuppressWarnings("rawtypes") private JList childList; // Used to check whether to update the image list private ArrayList<String> imageList = new ArrayList<String>(); private boolean updateChildren = true; /** * Instance constructor */ public Stack_Synchroniser() { super(TITLE); } /* * (non-Javadoc) * * @see ij.plugin.frame.PlugInFrame#run(java.lang.String) */ public void run(String arg) { UsageTracker.recordPlugin(this.getClass(), arg); if (WindowManager.getImageCount() == 0) { IJ.showMessage("No images opened."); return; } if (instance != null) { instance.toFront(); return; } instance = this; IJ.register(Stack_Synchroniser.class); WindowManager.addWindow(this); // Register to be notified of image changes ImagePlus.addImageListener(this); createFrame(); fillImagesList(); addKeyListener(IJ.getInstance()); pack(); Point loc = Prefs.getLocation(OPT_LOCATION); if (loc != null) setLocation(loc); else { GUI.center(this); } setVisible(true); } /* * (non-Javadoc) * * @see ij.plugin.frame.PlugInFrame#windowActivated(java.awt.event.WindowEvent) */ public void windowActivated(WindowEvent e) { super.windowActivated(e); fillImagesList(); WindowManager.setWindow(this); } /** * Populate the drop-down with the current valid images */ public void fillImagesList() { // Find the currently open images ArrayList<String> newImageList = new ArrayList<String>(); if (WindowManager.getImageCount() > 0) { for (int id : gdsc.core.ij.Utils.getIDList()) { ImagePlus imp = WindowManager.getImage(id); // Image must be a stack if (imp != null && imp.getStackSize() > 1) { String imageTitle = -imp.getID() + " : " + imp.getTitle(); newImageList.add(imageTitle); } } } // Check if the image list has changed if (imageList.equals(newImageList)) return; synchronized (imageChoice) { imageList = newImageList; // Re-populate the image lists imageChoice.removeAll(); updateChildren = false; for (String imageTitle : imageList) { imageChoice.add(imageTitle); } updateChildren = true; // Ensure the drop-downs are resized pack(); } fillChildList(); } @SuppressWarnings("unchecked") private void fillChildList() { synchronized (childList) { // Re-populate the image lists childList.clearSelection(); listModel.clear(); for (String imageTitle : imageList) { if (!imageTitle.equals(imageChoice.getSelectedItem())) { listModel.addElement(imageTitle); } } invalidate(); } } /* * (non-Javadoc) * * @see ij.plugin.frame.PlugInFrame#windowClosing(java.awt.event.WindowEvent) */ public void windowClosing(WindowEvent e) { Prefs.saveLocation(OPT_LOCATION, getLocation()); instance = null; ImagePlus.removeImageListener(this); super.close(); } @SuppressWarnings({ "unchecked", "rawtypes" }) private void createFrame() { Panel mainPanel = new Panel(); add(mainPanel); imageChoice = new Choice(); mainPanel.add(createChoicePanel(imageChoice, null, null, "Image")); imageChoice.addItemListener(this); synchroniseButton = new JToggleButton("Synchronise"); synchroniseButton.addItemListener(this); helpButton = new JButton("Help"); helpButton.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { String macro = "run('URL...', 'url="+gdsc.help.URL.UTILITY+"');"; new MacroRunner(macro); } }); JPanel buttonPanel = new JPanel(); FlowLayout l = new FlowLayout(); l.setVgap(0); buttonPanel.setLayout(l); buttonPanel.add(synchroniseButton, BorderLayout.CENTER); buttonPanel.add(helpButton, BorderLayout.CENTER); mainPanel.add(buttonPanel); mainPanel.add(createLabelPanel("Images to sync:")); listModel = new DefaultListModel(); childList = new JList(listModel); childList.setVisibleRowCount(15); JScrollPane scrollPane = new JScrollPane(childList); mainPanel.add(scrollPane); childList.addListSelectionListener(this); GridBagLayout mainGrid = new GridBagLayout(); int y = 0; GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.WEST; c.gridwidth = 1; c.insets = new Insets(2, 2, 2, 2); for (Component comp : mainPanel.getComponents()) { c.gridy = y++; mainGrid.setConstraints(comp, c); } mainPanel.setLayout(mainGrid); } private Panel createChoicePanel(Choice list, String[] options, String selected, String label) { Panel panel = new Panel(); panel.setLayout(new BorderLayout()); Label listLabel = new Label(label, 0); if (options != null) { for (String option : options) list.add(option); try { list.select(Integer.parseInt(selected)); } catch (Exception ex) { list.select((String) selected); } } panel.add(listLabel, BorderLayout.WEST); panel.add(list, BorderLayout.CENTER); return panel; } private Panel createLabelPanel(String label) { Panel panel = new Panel(); panel.setLayout(new BorderLayout()); Label listLabel = new Label(label, 0); panel.add(listLabel, BorderLayout.WEST); return panel; } /* * (non-Javadoc) * * @see ij.ImageListener#imageOpened(ij.ImagePlus) */ public void imageOpened(ImagePlus imp) { fillImagesList(); } /* * (non-Javadoc) * * @see ij.ImageListener#imageClosed(ij.ImagePlus) */ public void imageClosed(ImagePlus imp) { if (imp == parentImage) { parentImage = null; } else if (childImages.contains(imp)) { childImages.remove(imp); } fillImagesList(); } /* * (non-Javadoc) * * @see ij.ImageListener#imageUpdated(ij.ImagePlus) */ public void imageUpdated(ImagePlus imp) { if (imp == parentImage && currentSlice != parentImage.getCurrentSlice() && !childImages.isEmpty()) { currentSlice = parentImage.getCurrentSlice(); int channel = parentImage.getChannel(); int frame = parentImage.getFrame(); int slice = parentImage.getSlice(); // System.out.printf("Image Id %d : Slice %d (c=%d,z=%d,t=%d)\n", imp.getID(), currentSlice, channel, slice, // frame); for (ImagePlus childImp : childImages) { int stackIndex = childImp.getStackIndex(channel, slice, frame); childImp.setSlice(stackIndex); } } } /* * (non-Javadoc) * * @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent) */ public void itemStateChanged(ItemEvent e) { Object actioner = e.getSource(); if (actioner instanceof Choice) { if (updateChildren) { fillChildList(); } } parentImage = (synchroniseButton.isSelected()) ? WindowManager.getImage(extractId(this.imageChoice.getSelectedItem())) : null; updateSynchronisation(); } /* * (non-Javadoc) * * @see javax.swing.event.ListSelectionListener#valueChanged(javax.swing.event.ListSelectionEvent) */ public void valueChanged(ListSelectionEvent e) { updateSynchronisation(); } /** * Updates the list of child images that will be synchronised to the parent image */ private void updateSynchronisation() { if (parentImage != null) { currentSlice = -1; childImages.clear(); for (int index : childList.getSelectedIndices()) { String imageTitle = (String) listModel.get(index); ImagePlus imp = WindowManager.getImage(extractId(imageTitle)); if (imp != null) { childImages.add(imp); } } imageUpdated(parentImage); } } private int extractId(String imageTitle) { String[] data = imageTitle.split(" : "); int id = 0; try { id = Integer.parseInt(data[0]); } catch (NumberFormatException ex) { } return -id; } }