/* *------------------------------------------------------------------------------ * Copyright (C) 2006-2014 University of Dundee. All rights reserved. * * * 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. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.agents.imviewer.view; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.prefs.Preferences; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JSeparator; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.openmicroscopy.shoola.agents.events.iviewer.SaveRelatedData; import org.openmicroscopy.shoola.agents.imviewer.ImViewerAgent; import org.openmicroscopy.shoola.agents.imviewer.actions.ActivateRecentAction; import org.openmicroscopy.shoola.agents.imviewer.actions.ActivationAction; import omero.gateway.SecurityContext; import org.openmicroscopy.shoola.env.rnd.RndProxyDef; import org.openmicroscopy.shoola.env.ui.TaskBar; import omero.gateway.model.DataObject; import omero.gateway.model.ImageData; import org.openmicroscopy.shoola.util.CommonsLangUtils; /** * Factory to create {@link ImViewer} components. * This class keeps track of all {@link ImViewer} instances that have been * created and are not yet {@link ImViewer#DISCARDED discarded}. A new * component is only created if none of the <i>tracked</i> ones is already * displaying the given hierarchy. Otherwise, the existing component is * recycled. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Andrea Falconi      * <a href="mailto:a.falconi@dundee.ac.uk">a.falconi@dundee.ac.uk</a> * @author Donald MacDonald      * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a> * @version 3.0 * @since OME2.2 */ public class ImViewerFactory implements ChangeListener, PropertyChangeListener { /** * The name of the property, temporary solution before we have preferences. */ private static final String OMERO_VIEWER_COMPRESSION = "omeroViewerCompression"; /** The name of the interpolation property */ private static final String OMERO_INTERPOLATION = "omeroViewerInterpolation"; /** The name of the windows menu. */ private static final String MENU_NAME = "Image Viewer"; /** The name of the recent menu. */ private static final String RECENT_MENU = "Open Recent"; /** The name of the recent menu. */ private static final String CLEAR_MENU = "Clear menu"; /** The maximum number of recent items. */ private static final int MAX_RECENT = 10; /** The sole instance. */ private static final ImViewerFactory singleton = new ImViewerFactory(); /** * Adds all the {@link ImViewer} components that this factory is * currently tracking to the passed menu. * * @param menu The menu to add the components to. */ static void register(JMenu menu) { //return singleton.viewers; if (menu == null) return; Iterator<ImViewer> i = singleton.viewers.iterator(); menu.removeAll(); while (i.hasNext()) menu.add(new JMenuItem(new ActivationAction(i.next()))); int n = singleton.recentViewers.size(); if (n > 0) { Iterator<ImViewerRecentObject> j = singleton.recentViewers.iterator(); singleton.recentMenu.removeAll(); while (j.hasNext()) { singleton.recentMenu.add(new JMenuItem( new ActivateRecentAction(j.next()))); } singleton.recentMenu.add(new JSeparator()); singleton.recentMenu.add(singleton.clearMenu); menu.add(singleton.recentMenu); } } /** * Returns the <code>window</code> menu. * * @return See above. */ static JMenu getWindowMenu() { return singleton.windowMenu; } /** Attaches the {@link #windowMenu} to the <code>TaskBar</code>. */ static void attachWindowMenuToTaskBar() { if (singleton.isAttached) return; TaskBar tb = ImViewerAgent.getRegistry().getTaskBar(); tb.addToMenu(TaskBar.WINDOW_MENU, singleton.windowMenu); singleton.isAttached = true; } /** * Notifies the model that the user's group has successfully be modified * if the passed value is <code>true</code>, unsuccessfully * if <code>false</code>. * * @param success Pass <code>true</code> if successful, <code>false</code> * otherwise. */ public static void onGroupSwitched(boolean success) { if (!success) return; singleton.clear(); } /** * Returns a viewer to display the image corresponding to the specified id. * * @param ctx The security context. * @param image The image or wellSample to view. * @param bounds The bounds of the component invoking the * {@link ImViewer}. * @param separateWindow Pass <code>true</code> to open the viewer in a * separate window, <code>false</code> otherwise. * @return See above. */ public static ImViewer getImageViewer(SecurityContext ctx, DataObject image, Rectangle bounds, boolean separateWindow) { ImViewerModel model = new ImViewerModel(ctx, image, bounds, separateWindow); return singleton.getViewer(model); } /** * Returns a viewer to display the image corresponding to the specified id. * * @param ctx The security context. * @param imageID The image to view. * @param bounds The bounds of the component invoking the {@link ImViewer}. * @param separateWindow Pass <code>true</code> to open the viewer in a * separate window, <code>false</code> otherwise. * @return See above. */ public static ImViewer getImageViewer(SecurityContext ctx, long imageID, Rectangle bounds, boolean separateWindow) { ImViewerModel model = new ImViewerModel(ctx, imageID, bounds, separateWindow); return singleton.getViewer(model); } /** * Returns the viewer if any, identified by the passed pixels ID. * * @param ctx The security context. * @param pixelsID The Identifier of the pixels set. * @return See above. */ public static ImViewer getImageViewer(SecurityContext ctx, long pixelsID) { Iterator<ImViewer> v = singleton.viewers.iterator(); ImViewerComponent comp; while (v.hasNext()) { comp = (ImViewerComponent) v.next(); if (comp.getModel().isSame(pixelsID, ctx)) return comp; } return null; } /** * Returns the viewer if any, identified by the passed image's ID. * * @param ctx The security context. * @param imageID The Identifier of the image. * @return See above. */ public static ImViewer getImageViewerFromImage(SecurityContext ctx, long imageID) { Iterator<ImViewer> v = singleton.viewers.iterator(); ImViewerComponent comp; while (v.hasNext()) { comp = (ImViewerComponent) v.next(); if (comp.getModel().isSameImage(imageID, ctx)) return comp; } return null; } /** * Returns the viewer if any, identified by the passed pixels ID. * * @param parent The of the image. * @return See above. */ public static ImViewer getImageViewerFromParent(DataObject parent) { if (parent == null) return null; Iterator<ImViewer> v = singleton.viewers.iterator(); ImViewerComponent comp; while (v.hasNext()) { comp = (ImViewerComponent) v.next(); if (comp.getModel().isSameParent(parent)) return comp; } return null; } /** * Copies the rendering settings. * * @param image The image to copy the rendering settings from. * @param refRndDef 'Pending' rendering settings to copy (can be null) */ public static void copyRndSettings(ImageData image, RndProxyDef refRndDef) { singleton.refImage = image; singleton.refRndDef = refRndDef; Iterator<ImViewer> v = singleton.viewers.iterator(); ImViewerComponent comp; while (v.hasNext()) { comp = (ImViewerComponent) v.next(); if (image != null && comp.getModel().getImageID() != image.getId()) comp.copyRndSettings(); } } /** * Indicates that rendering settings has been saved using another way. * * @param pixelsID The Identifier of the pixels set. * @param settings The rendering settings saved. */ public static void rndSettingsSaved(long pixelsID, RndProxyDef settings) { Iterator<ImViewer> v = singleton.viewers.iterator(); ImViewerComponent comp; while (v.hasNext()) { comp = (ImViewerComponent) v.next(); if (comp.getModel().getPixelsID() == pixelsID) comp.onRndSettingsSaved(settings); comp.reloadRenderingThumbs(); } } /** * Indicates that rendering settings have been modified. * * @param imageID The Identifier of the pixels set. */ public static void rndSettingsChanged(long imageID) { Iterator<ImViewer> v = singleton.viewers.iterator(); ImViewerComponent comp; while (v.hasNext()) { comp = (ImViewerComponent) v.next(); if (comp.getModel().getImageID() == imageID) { comp.onSettingsChanged(); } } } /** * Stores the passed event in the correct viewer. * * @param evt The event to store. */ public static void storeEvent(SaveRelatedData evt) { Iterator<ImViewer> v = singleton.viewers.iterator(); ImViewerComponent comp; long pixelsID = evt.getPixelsID(); while (v.hasNext()) { comp = (ImViewerComponent) v.next(); if (comp.getModel().getPixelsID() == pixelsID) comp.storeEvent(evt); } } /** * Returns the instances to save. * * @return See above. */ public static List<Object> getInstancesToSave() { if (singleton.viewers.size() == 0) return null; List<Object> instances = new ArrayList<Object>(); Iterator<ImViewer> i = singleton.viewers.iterator(); ImViewerComponent comp; while (i.hasNext()) { comp = (ImViewerComponent) i.next(); if (comp.hasSettingsToSave()) instances.add(comp); } return instances; } /** * Saves the passed instances and discards them. * * @param instances The instances to save. */ public static void saveInstances(List<Object> instances) { //if (singleton.viewers.size() == 0) return; if (instances != null) { Iterator<Object> i = instances.iterator(); ImViewerComponent comp; Object o; List<Long> ids = new ArrayList<Long>(); while (i.hasNext()) { o = i.next(); if (o instanceof ImViewerComponent) { comp = (ImViewerComponent) o; comp.close(false); singleton.viewers.remove(comp); ids.add(comp.getModel().getImageID()); } } removeRecentViewers(ids); } } /** * Sets the display mode. * * @param displayMode The value to set. */ public static void setDisplayMode(int displayMode) { Iterator<ImViewer> i = singleton.viewers.iterator(); ImViewerComponent comp; while (i.hasNext()) { comp = (ImViewerComponent) i.next(); comp.setDisplayMode(displayMode); } } /** * Returns the image to copy the rendering settings from. * * @return See above. */ static ImageData getRefImage() { return singleton.refImage; } /** * Returns the copied 'pending' rendering settings. * * @return See above. */ static RndProxyDef getRefSettings() { return singleton.refRndDef; } /** * Returns the user preferences. * * @return See above. */ static ViewerPreferences getPreferences() { return singleton.pref; } /** * Sets the preferences. * * @param pref The value to set. */ static void setPreferences(ViewerPreferences pref) { singleton.pref = null;//pref; } /** * Sets the compression level. * * @param level The value to set. */ static void setCompressionLevel(int level) { Preferences p = Preferences.userNodeForPackage(ImViewerFactory.class); p.put(OMERO_VIEWER_COMPRESSION, ""+level); } /** * Returns the compression level. * * @return See above. */ static int getCompressionLevel() { Preferences p = Preferences.userNodeForPackage(ImViewerFactory.class); String value = p.get(OMERO_VIEWER_COMPRESSION, null); if (value != null && value.trim().length() > 0) return Integer.parseInt(value); return -1; } /** * Sets the interpolation user preference */ public static void setInterpolation(boolean interpolation) { Preferences p = Preferences.userNodeForPackage(ImViewerFactory.class); p.put(OMERO_INTERPOLATION, ""+interpolation); } /** * Returns the interpolation user preference or <code>null</code> * if it hasn't been set. * * @return See above. */ public static Boolean isInterpolation() { Preferences p = Preferences.userNodeForPackage(ImViewerFactory.class); String value = p.get(OMERO_INTERPOLATION, null); if (CommonsLangUtils.isNotEmpty(value)) return new Boolean(value); return null; } /** * Removes the viewers from the list of viewers recently opened. * * @param ids The identifiers of the viewers. */ private static void removeRecentViewers(List<Long> ids) { if (ids == null || ids.size() == 0) return; Iterator<ImViewerRecentObject> j = singleton.recentViewers.iterator(); ImViewerRecentObject recent; List<ImViewerRecentObject> toRemove = new ArrayList<ImViewerRecentObject>(); while (j.hasNext()) { recent = j.next(); if (ids.contains(recent.getImageID())) toRemove.add(recent); } singleton.recentViewers.removeAll(toRemove); } /** All the tracked components. */ private Set<ImViewer> viewers; /** Collection of image recently viewed. */ private List<ImViewerRecentObject> recentViewers; /** The windows menu. */ private JMenu windowMenu; /** The recent windows menu. */ private JMenu recentMenu; /** The clear menu items. */ private JMenuItem clearMenu; /** * Indicates if the {@link #windowMenu} is attached to the * <code>TaskBar</code>. */ private boolean isAttached; /** The image data to copy the rendering settings from. */ private ImageData refImage; /** 'Pending' rendering settings to copy */ private RndProxyDef refRndDef; /** The user preferences for the viewer. */ private ViewerPreferences pref; /** Creates a new instance. */ private ImViewerFactory() { viewers = new HashSet<ImViewer>(); recentViewers = new ArrayList<ImViewerRecentObject>(); isAttached = false; windowMenu = new JMenu(MENU_NAME); recentMenu = new JMenu(RECENT_MENU); clearMenu = new JMenuItem(CLEAR_MENU); clearMenu.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { singleton.recentViewers.clear(); } }); } /** Clears the collection of tracked viewers. */ private void clear() { Iterator<ImViewer> i = viewers.iterator(); ImViewerComponent comp; while (i.hasNext()) { comp = (ImViewerComponent) i.next(); comp.removeChangeListener(this); comp.discard(); } singleton.viewers.clear(); singleton.recentViewers.clear(); handleViewerDiscarded(); } /** * Checks the list of opened viewers before removing the entry from the * menu. */ private void handleViewerDiscarded() { if (!singleton.isAttached) return; if (singleton.viewers.size() != 0) return; TaskBar tb = ImViewerAgent.getRegistry().getTaskBar(); tb.removeFromMenu(TaskBar.WINDOW_MENU, singleton.windowMenu); singleton.isAttached = false; } /** * Creates or recycles a viewer component for the specified * <code>model</code>. * * @param model The component's Model. * @return A {@link ImViewer} for the specified <code>model</code>. */ private ImViewer getViewer(ImViewerModel model) { Iterator<ImViewer> v = viewers.iterator(); ImViewerComponent comp; while (v.hasNext()) { comp = (ImViewerComponent) v.next(); if (comp.getModel().getImageID() == model.getImageID()) return comp; } comp = new ImViewerComponent(model); comp.initialize(); comp.addChangeListener(this); comp.addPropertyChangeListener(this); viewers.add(comp); // long id = model.getImageID(); if (id < 0) return null; Iterator<ImViewerRecentObject> j = recentViewers.iterator(); ImViewerRecentObject obj; ImViewerRecentObject toRemove = null; while (j.hasNext()) { obj = j.next(); if (obj.getImageID() == id) toRemove = obj; } if (toRemove != null) recentViewers.remove(toRemove); return comp; } /** * Removes a viewer from the {@link #viewers} set when it is * {@link ImViewer#DISCARDED discarded}. * @see ChangeListener#stateChanged(ChangeEvent) */ public void stateChanged(ChangeEvent ce) { ImViewerComponent comp = (ImViewerComponent) ce.getSource(); switch (comp.getState()) { case ImViewer.DISCARDED: case ImViewer.CANCELLED: viewers.remove(comp); removeRecentViewers( Arrays.asList(comp.getModel().getImageID())); handleViewerDiscarded(); break; default: } } /** * Listens to the {@link ImViewer#RECENT_VIEWER_PROPERTY} * @see PropertyChangeListener#propertyChange(PropertyChangeEvent) */ public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); if (ImViewer.RECENT_VIEWER_PROPERTY.equals(name)) { Iterator<ImViewerRecentObject> i = recentViewers.iterator(); ImViewerRecentObject v = (ImViewerRecentObject) evt.getNewValue(); ImViewerRecentObject old; ImViewerRecentObject exist = null; while (i.hasNext()) { old = i.next(); if (old.getImageID() == v.getImageID()) { exist = old; break; } } if (exist != null) recentViewers.remove(exist); if (recentViewers.size() >= MAX_RECENT) recentViewers.remove(0); recentViewers.add(v); handleViewerDiscarded(); } } }