/******************************************************************************* * Copyright (c) 2016 Weasis Team and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.core.ui.editor.image; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GridBagConstraints; import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.Toolkit; import java.awt.Window; import java.awt.color.ColorSpace; import java.awt.event.FocusEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelListener; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.ColorModel; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.beans.PropertyChangeEvent; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import javax.media.jai.PlanarImage; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.TransferHandler; import javax.swing.border.BevelBorder; import javax.swing.border.Border; import javax.swing.border.EtchedBorder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.api.gui.Image2DViewer; import org.weasis.core.api.gui.model.ViewModel; import org.weasis.core.api.gui.util.ActionState; import org.weasis.core.api.gui.util.ActionW; import org.weasis.core.api.gui.util.ComboItemListener; import org.weasis.core.api.gui.util.Filter; import org.weasis.core.api.gui.util.JMVUtils; import org.weasis.core.api.gui.util.MathUtil; import org.weasis.core.api.gui.util.MouseActionAdapter; import org.weasis.core.api.gui.util.SliderChangeListener; import org.weasis.core.api.gui.util.WinUtil; import org.weasis.core.api.image.FilterOp; import org.weasis.core.api.image.FlipOp; import org.weasis.core.api.image.ImageOpEvent; import org.weasis.core.api.image.ImageOpNode; import org.weasis.core.api.image.OpManager; import org.weasis.core.api.image.PseudoColorOp; import org.weasis.core.api.image.RotationOp; import org.weasis.core.api.image.WindowOp; import org.weasis.core.api.image.ZoomOp; import org.weasis.core.api.image.op.ByteLut; import org.weasis.core.api.image.util.ImageFiler; import org.weasis.core.api.image.util.KernelData; import org.weasis.core.api.image.util.MeasurableLayer; import org.weasis.core.api.image.util.Unit; import org.weasis.core.api.media.data.ImageElement; import org.weasis.core.api.media.data.MediaSeries; import org.weasis.core.api.media.data.Series; import org.weasis.core.api.media.data.SeriesComparator; import org.weasis.core.api.media.data.TagW; import org.weasis.core.api.service.AuditLog; import org.weasis.core.api.util.FontTools; import org.weasis.core.api.util.StringUtil; import org.weasis.core.ui.Messages; import org.weasis.core.ui.docking.UIManager; import org.weasis.core.ui.editor.SeriesViewerEvent; import org.weasis.core.ui.editor.SeriesViewerEvent.EVENT; import org.weasis.core.ui.editor.image.SynchData.Mode; import org.weasis.core.ui.editor.image.dockable.MeasureTool; import org.weasis.core.ui.model.AbstractGraphicModel; import org.weasis.core.ui.model.GraphicModel; import org.weasis.core.ui.model.graphic.DragGraphic; import org.weasis.core.ui.model.graphic.Graphic; import org.weasis.core.ui.model.imp.XmlGraphicModel; import org.weasis.core.ui.model.layer.LayerAnnotation; import org.weasis.core.ui.model.layer.LayerType; import org.weasis.core.ui.model.layer.imp.RenderedImageLayer; import org.weasis.core.ui.model.utils.Draggable; import org.weasis.core.ui.model.utils.GraphicUtil; import org.weasis.core.ui.model.utils.bean.GraphicClipboard; import org.weasis.core.ui.model.utils.bean.PanPoint; import org.weasis.core.ui.model.utils.bean.PanPoint.State; import org.weasis.core.ui.model.utils.imp.DefaultViewModel; import org.weasis.core.ui.pref.Monitor; import org.weasis.core.ui.util.DefaultAction; import org.weasis.core.ui.util.MouseEventDouble; import org.weasis.core.ui.util.TitleMenuItem; /** * @author Nicolas Roduit, Benoit Jacquemoud */ public abstract class DefaultView2d<E extends ImageElement> extends GraphicsPane implements ViewCanvas<E> { private static final long serialVersionUID = 4546307243696460899L; private static final Logger LOGGER = LoggerFactory.getLogger(DefaultView2d.class); public enum ZoomType { CURRENT, BEST_FIT, PIXEL_SIZE, REAL } static final Shape[] pointer; static { pointer = new Shape[5]; pointer[0] = new Ellipse2D.Double(-27.0, -27.0, 54.0, 54.0); pointer[1] = new Line2D.Double(-40.0, 0.0, -5.0, 0.0); pointer[2] = new Line2D.Double(5.0, 0.0, 40.0, 0.0); pointer[3] = new Line2D.Double(0.0, -40.0, 0.0, -5.0); pointer[4] = new Line2D.Double(0.0, 5.0, 0.0, 40.0); } public static final String PROP_LAYER_OFFSET = "layer.offset"; //$NON-NLS-1$ public static final GraphicClipboard GRAPHIC_CLIPBOARD = new GraphicClipboard(); public static final Object antialiasingOff = RenderingHints.VALUE_ANTIALIAS_OFF; public static final Object antialiasingOn = RenderingHints.VALUE_ANTIALIAS_ON; public static final Cursor EDIT_CURSOR = DefaultView2d.getCustomCursor("editpoint.png", "Edit Point", 16, 16); //$NON-NLS-1$ //$NON-NLS-2$ public static final Cursor HAND_CURSOR = DefaultView2d.getCustomCursor("hand.gif", "hand", 16, 16); //$NON-NLS-1$ //$NON-NLS-2$ public static final Cursor WAIT_CURSOR = DefaultView2d.getNewCursor(Cursor.WAIT_CURSOR); public static final Cursor CROSS_CURSOR = DefaultView2d.getNewCursor(Cursor.CROSSHAIR_CURSOR); public static final Cursor MOVE_CURSOR = DefaultView2d.getNewCursor(Cursor.MOVE_CURSOR); public static final Cursor DEFAULT_CURSOR = DefaultView2d.getNewCursor(Cursor.DEFAULT_CURSOR); protected final FocusHandler focusHandler = new FocusHandler(); protected final GraphicMouseHandler<E> graphicMouseHandler; private final PanPoint highlightedPosition = new PanPoint(State.CENTER); private final PanPoint startedDragPoint = new PanPoint(State.DRAGSTART); private int pointerType = 0; protected final Color pointerColor1 = Color.black; protected final Color pointerColor2 = Color.white; protected final Border normalBorder = new EtchedBorder(BevelBorder.LOWERED, Color.gray, Color.white); protected final Border focusBorder = new EtchedBorder(BevelBorder.LOWERED, focusColor, focusColor); protected final Border lostFocusBorder = new EtchedBorder(BevelBorder.LOWERED, lostFocusColor, lostFocusColor); protected final RenderedImageLayer<E> imageLayer; protected Panner<E> panner; protected ZoomWin<E> lens; private final List<ViewButton> viewButtons; protected ViewButton synchButton; protected MediaSeries<E> series = null; protected LayerAnnotation infoLayer; protected int tileOffset; protected final ImageViewerEventManager<E> eventManager; public DefaultView2d(ImageViewerEventManager<E> eventManager) { this(eventManager, null); } public DefaultView2d(ImageViewerEventManager<E> eventManager, ViewModel viewModel) { super(viewModel); this.eventManager = Objects.requireNonNull(eventManager); this.viewButtons = new ArrayList<>(); this.tileOffset = 0; imageLayer = new RenderedImageLayer<>(true); initActionWState(); graphicMouseHandler = new GraphicMouseHandler<>(this); setBorder(normalBorder); setFocusable(true); // Must be larger to the screens to be resize correctly by the container setPreferredSize(new Dimension(4096, 4096)); setMinimumSize(new Dimension(50, 50)); } @Override public void registerDefaultListeners() { addFocusListener(this); ToolTipManager.sharedInstance().registerComponent(this); imageLayer.addLayerChangeListener(this); } @Override public JComponent getJComponent() { return this; } protected void buildPanner() { panner = Optional.ofNullable(panner).orElseGet(() -> new Panner<>(this)); } @Override public void copyActionWState(HashMap<String, Object> actionsInView) { actionsInView.putAll(this.actionsInView); } protected void initActionWState() { actionsInView.put(ActionW.SPATIAL_UNIT.cmd(), Unit.PIXEL); actionsInView.put(ZOOM_TYPE_CMD, ZoomType.BEST_FIT); actionsInView.put(ActionW.ZOOM.cmd(), 0.0); actionsInView.put(ActionW.LENS.cmd(), false); actionsInView.put(ActionW.DRAWINGS.cmd(), true); actionsInView.put(LayerType.CROSSLINES.name(), true); actionsInView.put(ActionW.INVERSESTACK.cmd(), false); actionsInView.put(ActionW.FILTERED_SERIES.cmd(), null); OpManager disOp = getDisplayOpManager(); disOp.setParamValue(WindowOp.OP_NAME, WindowOp.P_APPLY_WL_COLOR, eventManager.getOptions().getBooleanProperty(WindowOp.P_APPLY_WL_COLOR, true)); disOp.setParamValue(ZoomOp.OP_NAME, ZoomOp.P_INTERPOLATION, eventManager.getZoomSetting().getInterpolation()); disOp.setParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE, 0); disOp.setParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP, false); disOp.setParamValue(FilterOp.OP_NAME, FilterOp.P_KERNEL_DATA, KernelData.NONE); disOp.setParamValue(PseudoColorOp.OP_NAME, PseudoColorOp.P_LUT, ByteLut.defaultLUT); disOp.setParamValue(PseudoColorOp.OP_NAME, PseudoColorOp.P_LUT_INVERSE, false); } @Override public ImageViewerEventManager<E> getEventManager() { return eventManager; } @Override public void updateSynchState() { if (getActionValue(ActionW.SYNCH_LINK.cmd()) != null) { if (synchButton == null) { synchButton = new ViewButton(new ShowPopup() { @Override public void showPopup(Component invoker, int x, int y) { final SynchData synch = (SynchData) getActionValue(ActionW.SYNCH_LINK.cmd()); if (synch == null) { return; } JPopupMenu popupMenu = new JPopupMenu(); TitleMenuItem itemTitle = new TitleMenuItem(ActionW.SYNCH.getTitle(), popupMenu.getInsets()); popupMenu.add(itemTitle); popupMenu.addSeparator(); for (Entry<String, Boolean> a : synch.getActions().entrySet()) { JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(a.getKey(), a.getValue()); menuItem.addActionListener(e -> { if (e.getSource() instanceof JCheckBoxMenuItem) { JCheckBoxMenuItem item = (JCheckBoxMenuItem) e.getSource(); synch.getActions().put(item.getText(), item.isSelected()); } }); popupMenu.add(menuItem); } popupMenu.show(invoker, x, y); } }, SYNCH_ICON); synchButton.setVisible(true); synchButton.setPosition(GridBagConstraints.SOUTHEAST); } if (!getViewButtons().contains(synchButton)) { getViewButtons().add(synchButton); } SynchData synch = (SynchData) getActionValue(ActionW.SYNCH_LINK.cmd()); synchButton.setVisible(!SynchData.Mode.NONE.equals(synch.getMode())); } else { getViewButtons().remove(synchButton); } } protected PlanarImage getPreprocessedImage(E imageElement) { return imageElement.getImage((OpManager) actionsInView.get(ActionW.PREPROCESSING.cmd())); } protected void fillPixelInfo(final PixelInfo pixelInfo, final E imageElement, final double[] c) { if (c != null && c.length > 0) { pixelInfo.setValues(c); } } @Override public PixelInfo getPixelInfo(final Point p) { PixelInfo pixelInfo = new PixelInfo(); E imageElement = imageLayer.getSourceImage(); if (imageElement != null && imageLayer.getReadIterator() != null) { PlanarImage image = getPreprocessedImage(imageElement); // realPoint to handle special case: non square pixel image Point realPoint = new Point((int) Math.ceil(p.x / imageElement.getRescaleX() - 0.5), (int) Math.ceil(p.y / imageElement.getRescaleY() - 0.5)); Rectangle2D area = viewModel.getModelArea(); Point offset = getImageLayer().getOffset(); if (offset != null) { // Offset used for Crop operation area.setRect(offset.getX(), offset.getY(), area.getWidth(), area.getHeight()); } if (image != null && area.contains(p)) { try { realPoint.translate(-(int) area.getX(), -(int) area.getY()); if (image.getBounds().contains(realPoint)) { pixelInfo.setPosition(realPoint); pixelInfo.setPixelSpacingUnit(imageElement.getPixelSpacingUnit()); pixelInfo.setPixelSize(imageElement.getPixelSize()); double[] c = imageLayer.getReadIterator().getPixel(realPoint.x, realPoint.y, (double[]) null); pixelInfo.setPixelValueUnit(imageElement.getPixelValueUnit()); fillPixelInfo(pixelInfo, imageElement, c); if (c != null && c.length >= 1) { pixelInfo.setChannelNames(getChannelNames(image)); } } } catch (OutOfMemoryError e) { LOGGER.error("Get pixel value", e);//$NON-NLS-1$ // when image tile is not available anymore (file stream closed) System.gc(); try { Thread.sleep(100); } catch (InterruptedException et) { } } catch (Exception e) { LOGGER.error("Get pixel value", e);//$NON-NLS-1$ } } } return pixelInfo; } protected static String[] getChannelNames(PlanarImage image) { if (image != null) { ColorModel cm = image.getColorModel(); if (cm != null) { ColorSpace space = cm.getColorSpace(); if (space != null) { String[] val = new String[space.getNumComponents()]; for (int i = 0; i < val.length; i++) { val[i] = space.getName(i); } return val; } } } return null; } protected static class BulkDragSequence implements Draggable { private final List<Draggable> childDS; BulkDragSequence(List<DragGraphic> dragGraphList, MouseEventDouble mouseEvent) { childDS = new ArrayList<>(dragGraphList.size()); for (DragGraphic dragGraph : dragGraphList) { Draggable dragsequence = dragGraph.createMoveDrag(); if (dragsequence != null) { childDS.add(dragsequence); } } } @Override public void startDrag(MouseEventDouble mouseevent) { int i = 0; for (int j = childDS.size(); i < j; i++) { (childDS.get(i)).startDrag(mouseevent); } } @Override public void drag(MouseEventDouble mouseevent) { int i = 0; for (int j = childDS.size(); i < j; i++) { (childDS.get(i)).drag(mouseevent); } } @Override public Boolean completeDrag(MouseEventDouble mouseevent) { int i = 0; for (int j = childDS.size(); i < j; i++) { (childDS.get(i)).completeDrag(mouseevent); } return true; } } @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public Panner getPanner() { return panner; } @Override public void closeLens() { if (lens != null) { lens.showLens(false); this.remove(lens); actionsInView.put(ActionW.LENS.cmd(), false); lens = null; } } @Override public void setSeries(MediaSeries<E> series) { setSeries(series, null); } @Override public void setSeries(MediaSeries<E> newSeries, E selectedMedia) { MediaSeries<E> oldsequence = this.series; this.series = newSeries; if (oldsequence == null && newSeries == null) { return; } if (oldsequence != null && oldsequence.equals(newSeries) && imageLayer.getSourceImage() != null) { return; } closingSeries(oldsequence); // Preserve show lens property Object showLens = actionsInView.get(ActionW.LENS.cmd()); initActionWState(); actionsInView.put(ActionW.LENS.cmd(), showLens); try { if (newSeries == null) { setImage(null); } else { E media = selectedMedia; if (selectedMedia == null) { media = newSeries.getMedia(tileOffset < 0 ? 0 : tileOffset, (Filter<E>) actionsInView.get(ActionW.FILTERED_SERIES.cmd()), getCurrentSortComparator()); } imageLayer.fireOpEvent(new ImageOpEvent(ImageOpEvent.OpEvent.SeriesChange, series, media, null)); if (lens != null) { lens.setFreezeImage(null); } setImage(media); } } catch (Exception e) { AuditLog.logError(LOGGER, e, "Unexpected error:"); //$NON-NLS-1$ imageLayer.setImage(null, null); closeLens(); } finally { eventManager.updateComponentsListener(this); } // Set the sequence to the state OPEN if (newSeries != null) { newSeries.setOpen(true); } } protected void closingSeries(MediaSeries<E> mediaSeries) { if (mediaSeries == null) { return; } boolean open = false; synchronized (UIManager.VIEWER_PLUGINS) { List<ViewerPlugin<?>> plugins = UIManager.VIEWER_PLUGINS; pluginList: for (final ViewerPlugin<?> plugin : plugins) { List<? extends MediaSeries<?>> openSeries = plugin.getOpenSeries(); if (openSeries != null) { for (MediaSeries<?> s : openSeries) { if (mediaSeries == s) { // The sequence is still open in another view or plugin open = true; break pluginList; } } } } } mediaSeries.setOpen(open); // TODO setSelected and setFocused must be global to all view as open mediaSeries.setSelected(false, null); mediaSeries.setFocused(false); } @Override public void setFocused(Boolean focused) { if (series != null) { series.setFocused(focused); } if (focused && getBorder() == lostFocusBorder) { setBorder(focusBorder); } else if (!focused && getBorder() == focusBorder) { setBorder(lostFocusBorder); } } protected int getImageSize(E img, TagW tag1, TagW tag2) { Integer size = (Integer) img.getTagValue(tag1); if (size == null) { size = (Integer) img.getTagValue(tag2); } return (size == null) ? ImageFiler.TILESIZE : size; } protected Rectangle getImageBounds(E img) { if (img != null) { RenderedImage source = getPreprocessedImage(img); // Get the displayed width (adapted in case of the aspect ratio is not 1/1) boolean nosquarePixel = MathUtil.isDifferent(img.getRescaleX(), img.getRescaleY()); int width = source == null || nosquarePixel ? img.getRescaleWidth(getImageSize(img, TagW.ImageWidth, TagW.get("Columns"))) : source.getWidth(); //$NON-NLS-1$ int height = source == null || nosquarePixel ? img.getRescaleHeight(getImageSize(img, TagW.ImageHeight, TagW.get("Rows"))) : source.getHeight(); //$NON-NLS-1$ return new Rectangle(0, 0, width, height); } return new Rectangle(0, 0, 512, 512); } protected void updateCanvas(E img, boolean triggerViewModelChangeListeners) { final Rectangle modelArea = getImageBounds(img); if (!modelArea.equals(getViewModel().getModelArea())) { DefaultViewModel m = (DefaultViewModel) getViewModel(); boolean oldVal = m.isEnableViewModelChangeListeners(); if (!triggerViewModelChangeListeners) { m.setEnableViewModelChangeListeners(false); } m.adjustMinViewScaleFromImage(modelArea.width, modelArea.height); m.setModelArea(modelArea); if (!triggerViewModelChangeListeners) { m.setEnableViewModelChangeListeners(oldVal); } } } @Override public void updateCanvas(boolean triggerViewModelChangeListeners) { updateCanvas(getImage(), triggerViewModelChangeListeners); } protected void setImage(E img) { boolean updateGraphics = false; imageLayer.setEnableDispOperations(false); if (img == null) { actionsInView.put(ActionW.SPATIAL_UNIT.cmd(), Unit.PIXEL); ActionState spUnitAction = eventManager.getAction(ActionW.SPATIAL_UNIT); if (spUnitAction instanceof ComboItemListener) { ((ComboItemListener) spUnitAction) .setSelectedItemWithoutTriggerAction(actionsInView.get(ActionW.SPATIAL_UNIT.cmd())); } // Force the update for null image imageLayer.setEnableDispOperations(true); imageLayer.setImage(null, null); imageLayer.setEnableDispOperations(false); setGraphicManager(new XmlGraphicModel()); closeLens(); } else { E oldImage = imageLayer.getSourceImage(); if (img != null && !img.equals(oldImage)) { updateGraphics = true; actionsInView.put(ActionW.SPATIAL_UNIT.cmd(), img.getPixelSpacingUnit()); if (eventManager.getSelectedViewPane() == this) { ActionState spUnitAction = eventManager.getAction(ActionW.SPATIAL_UNIT); if (spUnitAction instanceof ComboItemListener) { ((ComboItemListener) spUnitAction) .setSelectedItemWithoutTriggerAction(actionsInView.get(ActionW.SPATIAL_UNIT.cmd())); } } actionsInView.put(ActionW.PREPROCESSING.cmd(), null); ActionState spUnitAction = eventManager.getAction(ActionW.SPATIAL_UNIT); if (spUnitAction instanceof ComboItemListener) { ((ComboItemListener) spUnitAction) .setSelectedItemWithoutTriggerAction(actionsInView.get(ActionW.SPATIAL_UNIT.cmd())); } updateCanvas(img, false); imageLayer.fireOpEvent(new ImageOpEvent(ImageOpEvent.OpEvent.ImageChange, series, img, null)); resetZoom(); // Update zoom operation to the current image (Reset update to the previous one) ImageOpNode node = imageLayer.getDisplayOpManager().getNode(ZoomOp.OP_NAME); if (node != null) { double viewScale = getViewModel().getViewScale(); node.setParam(ZoomOp.P_RATIO_X, viewScale * img.getRescaleX()); node.setParam(ZoomOp.P_RATIO_Y, viewScale * img.getRescaleY()); } imageLayer.setImage(img, (OpManager) actionsInView.get(ActionW.PREPROCESSING.cmd())); if (AuditLog.LOGGER.isInfoEnabled()) { PlanarImage image = img.getImage(); if (image != null) { StringBuilder pixSize = new StringBuilder(); SampleModel sm = image.getSampleModel(); if (sm != null) { int[] spsize = sm.getSampleSize(); if (spsize != null && spsize.length > 0) { pixSize.append(spsize[0]); for (int i = 1; i < spsize.length; i++) { pixSize.append(','); pixSize.append(spsize[i]); } } } AuditLog.LOGGER.info("open:image size:{},{} depth:{}", //$NON-NLS-1$ new Object[] { image.getWidth(), image.getHeight(), pixSize.toString() }); } } } // Apply all image processing operation for visualization imageLayer.setEnableDispOperations(true); if (updateGraphics) { GraphicModel modelList = (GraphicModel) img.getTagValue(TagW.PresentationModel); // After getting a new image iterator, update the measurements if (modelList == null) { modelList = new XmlGraphicModel(img); img.setTag(TagW.PresentationModel, modelList); } setGraphicManager(modelList); } if (panner != null) { panner.updateImage(); } if (lens != null) { lens.updateImage(); lens.updateZoom(); } } } @Override public double getBestFitViewScale() { return adjustViewScale(super.getBestFitViewScale()); } @Override public double getRealWorldViewScale() { double viewScale = 0.0; E img = getImage(); if (img != null) { Window win = SwingUtilities.getWindowAncestor(this); if (win != null) { GraphicsConfiguration config = win.getGraphicsConfiguration(); Monitor monitor = MeasureTool.viewSetting.getMonitor(config.getDevice()); if (monitor != null) { double realFactor = monitor.getRealScaleFactor(); if (realFactor > 0.0) { Unit imgUnit = img.getPixelSpacingUnit(); if (!Unit.PIXEL.equals(imgUnit)) { viewScale = imgUnit.getConvFactor() * img.getPixelSize() / realFactor; viewScale = -adjustViewScale(viewScale); } } } } } return viewScale; } protected double adjustViewScale(double viewScale) { double ratio = viewScale; if (ratio < DefaultViewModel.SCALE_MIN) { ratio = DefaultViewModel.SCALE_MIN; } else if (ratio > DefaultViewModel.SCALE_MAX) { ratio = DefaultViewModel.SCALE_MAX; } ActionState zoom = eventManager.getAction(ActionW.ZOOM); if (zoom instanceof SliderChangeListener) { SliderChangeListener z = (SliderChangeListener) zoom; // Adjust the best fit value according to the possible range of the model zoom action. if (eventManager.getSelectedViewPane() == this) { // Set back the value to UI components as this value cannot be computed early. z.setRealValue(ratio, false); ratio = z.getRealValue(); } else { ratio = z.toModelValue(z.toSliderValue(ratio)); } } return ratio; } @SuppressWarnings("rawtypes") protected boolean isDrawActionActive() { ViewerPlugin container = WinUtil.getParentOfClass(this, ViewerPlugin.class); if (container != null) { final ViewerToolBar toolBar = container.getViewerToolBar(); if (toolBar != null) { return toolBar.isCommandActive(ActionW.MEASURE.cmd()) || toolBar.isCommandActive(ActionW.DRAW.cmd()); } } return false; } @Override public RenderedImageLayer<E> getImageLayer() { return imageLayer; } @Override public MeasurableLayer getMeasurableLayer() { return imageLayer; } @Override public LayerAnnotation getInfoLayer() { return infoLayer; } @Override public int getTileOffset() { return tileOffset; } @Override public void setTileOffset(int tileOffset) { this.tileOffset = tileOffset; } @Override public MediaSeries<E> getSeries() { return series; } @Override public E getImage() { return imageLayer.getSourceImage(); } @Override public RenderedImage getSourceImage() { E image = getImage(); return image == null ? null : getPreprocessedImage(image); } @Override public final void center() { Rectangle2D bound = getViewModel().getModelArea(); setCenter(bound.getWidth() / 2.0, bound.getHeight() / 2.0); } @Override public final void setCenter(Double x, Double y) { int w = getWidth(); int h = getHeight(); // Only apply when the panel size is not zero. if (w != 0 && h != 0) { double scale = getViewModel().getViewScale(); setOrigin(x - (w - 1) / (2.0 * scale), y - (h - 1) / (2.0 * scale)); } } /** Provides panning */ public final void setOrigin(Double x, Double y) { getViewModel().setModelOffset(x, y); Optional.ofNullable(panner).ifPresent(p -> p.updateImageSize()); Optional.ofNullable(lens).ifPresent(l -> l.updateZoom()); } /** Provides panning */ public final void moveOrigin(double x, double y) { setOrigin(getViewModel().getModelOffsetX() + x, getViewModel().getModelOffsetY() + y); } @Override public final void moveOrigin(PanPoint point) { if (point != null) { if (PanPoint.State.CENTER.equals(point.getState())) { highlightedPosition.setHighlightedPosition(point.isHighlightedPosition()); highlightedPosition.setLocation(point); setCenter(point.getX(), point.getY()); } else if (PanPoint.State.MOVE.equals(point.getState())) { moveOrigin(point.getX(), point.getY()); } else if (PanPoint.State.DRAGSTART.equals(point.getState())) { startedDragPoint.setLocation(getViewModel().getModelOffsetX(), getViewModel().getModelOffsetY()); } else if (PanPoint.State.DRAGGING.equals(point.getState())) { setOrigin(startedDragPoint.getX() + point.getX(), startedDragPoint.getY() + point.getY()); } } } @Override public Comparator<E> getCurrentSortComparator() { SeriesComparator<E> sort = (SeriesComparator<E>) actionsInView.get(ActionW.SORTSTACK.cmd()); Boolean reverse = (Boolean) actionsInView.get(ActionW.INVERSESTACK.cmd()); return (reverse != null && reverse) ? sort.getReversOrderComparator() : sort; } @Override public int getFrameIndex() { if (series instanceof Series) { return ((Series<E>) series).getImageIndex(imageLayer.getSourceImage(), (Filter<E>) actionsInView.get(ActionW.FILTERED_SERIES.cmd()), getCurrentSortComparator()); } return -1; } @Override public void setActionsInView(String action, Object value) { setActionsInView(action, value, false); } @Override public void setActionsInView(String action, Object value, Boolean repaint) { if (action != null) { actionsInView.put(action, value); if (repaint) { repaint(); } } } @Override public void setSelected(Boolean selected) { setBorder(selected ? focusBorder : normalBorder); // Remove the selection of graphics graphicManager.setSelectedGraphic(null); // Throws to the tool listener the current graphic selection. graphicManager.fireGraphicsSelectionChanged(imageLayer); if (selected && series != null) { AuditLog.LOGGER.info("select:series nb:{}", series.getSeriesNumber()); //$NON-NLS-1$ } } @Override public Font getFont() { // required when used getGraphics().getFont() in DefaultGraphicLabel return MeasureTool.viewSetting.getFont(); } @Override public Font getLayerFont() { int fontSize = // Set font size according to the view size (int) Math .ceil(10 / ((this.getGraphics().getFontMetrics(FontTools.getFont12()).stringWidth("0123456789") * 7.0) //$NON-NLS-1$ / getWidth())); fontSize = fontSize < 6 ? 6 : fontSize > 16 ? 16 : fontSize; return new Font("SansSerif", 0, fontSize); //$NON-NLS-1$ } /** paint routine */ @Override public synchronized void paintComponent(Graphics g) { if (g instanceof Graphics2D) { draw((Graphics2D) g); } } protected void draw(Graphics2D g2d) { Stroke oldStroke = g2d.getStroke(); Paint oldColor = g2d.getPaint(); double viewScale = getViewModel().getViewScale(); double offsetX = getViewModel().getModelOffsetX() * viewScale; double offsetY = getViewModel().getModelOffsetY() * viewScale; // Paint the visible area g2d.translate(-offsetX, -offsetY); // Set font size for computing shared text areas that need to be repainted in different zoom magnitudes. Font defaultFont = getFont(); g2d.setFont(defaultFont); imageLayer.drawImage(g2d); drawLayers(g2d, affineTransform, inverseTransform); g2d.translate(offsetX, offsetY); drawPointer(g2d); if (infoLayer != null) { g2d.setFont(getLayerFont()); infoLayer.paint(g2d); } drawOnTop(g2d); g2d.setFont(defaultFont); g2d.setPaint(oldColor); g2d.setStroke(oldStroke); } protected void drawOnTop(Graphics2D g2d) { } @Override public void drawLayers(Graphics2D g2d, AffineTransform transform, AffineTransform inverseTransform) { if ((Boolean) actionsInView.get(ActionW.DRAWINGS.cmd())) { graphicManager.draw(g2d, transform, inverseTransform, new Rectangle2D.Double(modelToViewLength(getViewModel().getModelOffsetX()), modelToViewLength(getViewModel().getModelOffsetY()), getWidth(), getHeight())); } } @Override public void zoom(Double viewScale) { boolean defSize = MathUtil.isEqualToZero(viewScale); ZoomType type = (ZoomType) actionsInView.get(ZOOM_TYPE_CMD); double ratio = viewScale; if (defSize) { if (ZoomType.BEST_FIT.equals(type)) { ratio = -getBestFitViewScale(); } else if (ZoomType.REAL.equals(type)) { ratio = -getRealWorldViewScale(); } if (MathUtil.isEqualToZero(ratio)) { ratio = -adjustViewScale(1.0); } } actionsInView.put(ActionW.ZOOM.cmd(), ratio); super.zoom(Math.abs(ratio)); if (defSize) { /* * If the view has not been repainted once (the width and the height of the view is 0), it will be done * later and the componentResized event will call again the zoom. */ center(); } updateAffineTransform(); if (panner != null) { panner.updateImageSize(); } ImageOpNode node = imageLayer.getDisplayOpManager().getNode(ZoomOp.OP_NAME); E img = getImage(); if (img != null && node != null) { node.setParam(ZoomOp.P_RATIO_X, ratio * img.getRescaleX()); node.setParam(ZoomOp.P_RATIO_Y, ratio * img.getRescaleY()); imageLayer.updateDisplayOperations(); } } protected void updateAffineTransform() { Rectangle2D modelArea = getViewModel().getModelArea(); double viewScale = getViewModel().getViewScale(); affineTransform.setToScale(viewScale, viewScale); OpManager dispOp = getDisplayOpManager(); Boolean flip = JMVUtils.getNULLtoFalse(dispOp.getParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP)); Integer rotationAngle = (Integer) dispOp.getParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE); if (rotationAngle != null && rotationAngle > 0) { if (flip != null && flip) { rotationAngle = 360 - rotationAngle; } affineTransform.rotate(Math.toRadians(rotationAngle), modelArea.getWidth() / 2.0, modelArea.getHeight() / 2.0); } if (flip != null && flip) { // Using only one allows to enable or disable flip with the rotation action // case FlipMode.TOP_BOTTOM: // at = new AffineTransform(new double[] {1.0,0.0,0.0,-1.0}); // at.translate(0.0, -imageHt); // break; // case FlipMode.LEFT_RIGHT : // at = new AffineTransform(new double[] {-1.0,0.0,0.0,1.0}); // at.translate(-imageWid, 0.0); // break; // case FlipMode.TOP_BOTTOM_LEFT_RIGHT: // at = new AffineTransform(new double[] {-1.0,0.0,0.0,-1.0}); // at.translate(-imageWid, -imageHt); affineTransform.scale(-1.0, 1.0); affineTransform.translate(-modelArea.getWidth(), 0.0); } Point offset = getImageLayer().getOffset(); if (offset != null) { affineTransform.translate(-offset.getX(), -offset.getY()); } try { inverseTransform.setTransform(affineTransform.createInverse()); } catch (NoninvertibleTransformException e) { LOGGER.error("Create inverse transform", e); //$NON-NLS-1$ } } @Override public void setDrawingsVisibility(Boolean visible) { if ((Boolean) actionsInView.get(ActionW.DRAWINGS.cmd()) != visible) { actionsInView.put(ActionW.DRAWINGS.cmd(), visible); repaint(); } } @Override public Object getLensActionValue(String action) { if (lens == null) { return null; } return lens.getActionValue(action); } @Override public void changeZoomInterpolation(Integer interpolation) { Integer val = (Integer) getDisplayOpManager().getParamValue(ZoomOp.OP_NAME, ZoomOp.P_INTERPOLATION); boolean update = val == null || val != interpolation; if (update) { getDisplayOpManager().setParamValue(ZoomOp.OP_NAME, ZoomOp.P_INTERPOLATION, interpolation); if (lens != null) { lens.getDisplayOpManager().setParamValue(ZoomOp.OP_NAME, ZoomOp.P_INTERPOLATION, interpolation); lens.updateZoom(); } imageLayer.updateDisplayOperations(); } } @Override public OpManager getDisplayOpManager() { return imageLayer.getDisplayOpManager(); } @Override public void propertyChange(PropertyChangeEvent evt) { if (series == null) { return; } RenderedImage dispImage = imageLayer.getDisplayImage(); OpManager manager = imageLayer.getDisplayOpManager(); final String command = evt.getPropertyName(); if (command.equals(ActionW.SYNCH.cmd())) { SynchEvent synch = (SynchEvent) evt.getNewValue(); if (synch instanceof SynchCineEvent) { SynchCineEvent value = (SynchCineEvent) synch; E imgElement = getImage(); graphicManager.deleteByLayerType(LayerType.CROSSLINES); if (value.getView() == this) { if (tileOffset != 0) { // Index could have changed when loading series. imgElement = series.getMedia(value.getSeriesIndex() + tileOffset, (Filter<E>) actionsInView.get(ActionW.FILTERED_SERIES.cmd()), getCurrentSortComparator()); } else if (value.getMedia() instanceof ImageElement) { imgElement = (E) value.getMedia(); } } else if (value.getLocation() != null) { Boolean cutlines = (Boolean) actionsInView.get(ActionW.SYNCH_CROSSLINE.cmd()); if (cutlines != null && cutlines) { if (JMVUtils.getNULLtoTrue(actionsInView.get(LayerType.CROSSLINES.name()))) { // Compute cutlines from the location of selected image computeCrosslines(value.getLocation().doubleValue()); } } else { double location = value.getLocation().doubleValue(); // TODO add a way in GUI to resynchronize series. Offset should be in Series tag and related // to // a specific series // Double offset = (Double) actionsInView.get(ActionW.STACK_OFFSET.cmd()); // if (offset != null) { // location += offset; // } imgElement = series.getNearestImage(location, tileOffset, (Filter<E>) actionsInView.get(ActionW.FILTERED_SERIES.cmd()), getCurrentSortComparator()); AuditLog.LOGGER.info("synch:series nb:{}", series.getSeriesNumber()); //$NON-NLS-1$ } } else { // When no 3D information on the slice position imgElement = series.getMedia(value.getSeriesIndex() + tileOffset, (Filter<E>) actionsInView.get(ActionW.FILTERED_SERIES.cmd()), getCurrentSortComparator()); AuditLog.LOGGER.info("synch:series nb:{}", series.getSeriesNumber()); //$NON-NLS-1$ } Double zoomFactor = (Double) actionsInView.get(ActionW.ZOOM.cmd()); // Avoid to reset zoom when the mode is not best fit if (zoomFactor != null && zoomFactor >= 0.0) { Object zoomType = actionsInView.get(ViewCanvas.ZOOM_TYPE_CMD); actionsInView.put(ViewCanvas.ZOOM_TYPE_CMD, ZoomType.CURRENT); setImage(imgElement); actionsInView.put(ViewCanvas.ZOOM_TYPE_CMD, zoomType); } else { setImage(imgElement); } } else { propertyChange(synch); } } else if (command.equals(ActionW.IMAGE_PIX_PADDING.cmd())) { if (manager.setParamValue(WindowOp.OP_NAME, command, evt.getNewValue())) { imageLayer.updateDisplayOperations(); } } else if (command.equals(ActionW.PROGRESSION.cmd())) { actionsInView.put(command, evt.getNewValue()); imageLayer.updateDisplayOperations(); } if (Objects.nonNull(lens) && !Objects.equals(dispImage, imageLayer.getDisplayImage())) { /* * Transmit to the lens the command in case the source image has been freeze (for updating rotation and flip * => will keep consistent display) */ lens.setCommandFromParentView(command, evt.getNewValue()); lens.updateZoom(); } } private void propertyChange(final SynchEvent synch) { SynchData synchData = (SynchData) actionsInView.get(ActionW.SYNCH_LINK.cmd()); if (synchData != null && Mode.NONE.equals(synchData.getMode())) { return; } OpManager manager = imageLayer.getDisplayOpManager(); for (Entry<String, Object> entry : synch.getEvents().entrySet()) { String command = entry.getKey(); if (synchData != null && !synchData.isActionEnable(command)) { continue; } if (command.equals(ActionW.WINDOW.cmd()) || command.equals(ActionW.LEVEL.cmd())) { if (manager.setParamValue(WindowOp.OP_NAME, command, ((Number) entry.getValue()).doubleValue())) { imageLayer.updateDisplayOperations(); } } else if (command.equals(ActionW.ROTATION.cmd())) { if (manager.setParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE, entry.getValue())) { imageLayer.updateDisplayOperations(); updateAffineTransform(); } } else if (command.equals(ActionW.RESET.cmd())) { reset(); } else if (command.equals(ActionW.ZOOM.cmd())) { double val = (Double) entry.getValue(); // Special Cases: -200.0 => best fit, -100.0 => real world size if (MathUtil.isDifferent(val, -200.0) && MathUtil.isDifferent(val, -100.0)) { zoom(val); } else { Object zoomType = actionsInView.get(ViewCanvas.ZOOM_TYPE_CMD); actionsInView.put(ViewCanvas.ZOOM_TYPE_CMD, MathUtil.isEqual(val, -100.0) ? ZoomType.REAL : ZoomType.BEST_FIT); zoom(0.0); actionsInView.put(ViewCanvas.ZOOM_TYPE_CMD, zoomType); } } else if (command.equals(ActionW.LENSZOOM.cmd())) { if (lens != null) { lens.setActionInView(ActionW.ZOOM.cmd(), entry.getValue()); lens.updateZoom(); } } else if (command.equals(ActionW.LENS.cmd())) { Boolean showLens = (Boolean) entry.getValue(); actionsInView.put(command, showLens); if (showLens) { if (lens == null) { lens = new ZoomWin<>(this); } // resize if to big int maxWidth = getWidth() / 3; int maxHeight = getHeight() / 3; lens.setSize(lens.getWidth() > maxWidth ? maxWidth : lens.getWidth(), lens.getHeight() > maxHeight ? maxHeight : lens.getHeight()); this.add(lens); lens.showLens(true); } else { closeLens(); } } else if (command.equals(ActionW.PAN.cmd())) { Object point = entry.getValue(); // ImageViewerPlugin<E> view = eventManager.getSelectedView2dContainer(); // if (view != null) { // if(!view.getSynchView().isActionEnable(ActionW.ROTATION)){ // // } // } if (point instanceof PanPoint) { moveOrigin((PanPoint) entry.getValue()); } } else if (command.equals(ActionW.FLIP.cmd())) { // Horizontal flip is applied after rotation (To be compliant with DICOM PR) if (manager.setParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP, entry.getValue())) { imageLayer.updateDisplayOperations(); updateAffineTransform(); } } else if (command.equals(ActionW.LUT.cmd())) { if (manager.setParamValue(PseudoColorOp.OP_NAME, PseudoColorOp.P_LUT, entry.getValue())) { imageLayer.updateDisplayOperations(); } } else if (command.equals(ActionW.INVERT_LUT.cmd())) { if (manager.setParamValue(WindowOp.OP_NAME, command, entry.getValue())) { manager.setParamValue(PseudoColorOp.OP_NAME, PseudoColorOp.P_LUT_INVERSE, entry.getValue()); // Update VOI LUT if pixel padding imageLayer.updateDisplayOperations(); } } else if (command.equals(ActionW.FILTER.cmd())) { if (manager.setParamValue(FilterOp.OP_NAME, FilterOp.P_KERNEL_DATA, entry.getValue())) { imageLayer.updateDisplayOperations(); } } else if (command.equals(ActionW.SPATIAL_UNIT.cmd())) { actionsInView.put(command, entry.getValue()); // TODO update only measure and limit when selected view share graphics graphicManager.updateLabels(Boolean.TRUE, this); } } } protected void computeCrosslines(double location) { } @Override public void disposeView() { disableMouseAndKeyListener(); removeFocusListener(this); ToolTipManager.sharedInstance().unregisterComponent(this); imageLayer.removeLayerChangeListener(this); Optional.ofNullable(lens).ifPresent(l -> l.showLens(false)); if (series != null) { closingSeries(series); series = null; } super.disposeView(); } @Override public synchronized void disableMouseAndKeyListener() { MouseListener[] listener = this.getMouseListeners(); MouseMotionListener[] motionListeners = this.getMouseMotionListeners(); KeyListener[] keyListeners = this.getKeyListeners(); MouseWheelListener[] wheelListeners = this.getMouseWheelListeners(); for (int i = 0; i < listener.length; i++) { this.removeMouseListener(listener[i]); } for (int i = 0; i < motionListeners.length; i++) { this.removeMouseMotionListener(motionListeners[i]); } for (int i = 0; i < keyListeners.length; i++) { this.removeKeyListener(keyListeners[i]); } for (int i = 0; i < wheelListeners.length; i++) { this.removeMouseWheelListener(wheelListeners[i]); } Optional.ofNullable(lens).ifPresent(l -> l.disableMouseAndKeyListener()); } @Override public synchronized void iniDefaultMouseListener() { // focus listener is always on this.addMouseListener(focusHandler); this.addMouseMotionListener(focusHandler); } @Override public synchronized void iniDefaultKeyListener() { this.addKeyListener(this); } @Override public void keyTyped(KeyEvent e) { } @Override public void keyReleased(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_C && e.isControlDown()) { final ViewTransferHandler imageTransferHandler = new ViewTransferHandler(); imageTransferHandler.exportToClipboard(DefaultView2d.this, Toolkit.getDefaultToolkit().getSystemClipboard(), TransferHandler.COPY); } } @Override public void keyPressed(KeyEvent e) { if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_SPACE) { eventManager.nextLeftMouseAction(); } else if (e.getModifiers() == 0 && (e.getKeyCode() == KeyEvent.VK_SPACE || e.getKeyCode() == KeyEvent.VK_I)) { eventManager.fireSeriesViewerListeners( new SeriesViewerEvent(eventManager.getSelectedView2dContainer(), null, null, EVENT.TOOGLE_INFO)); } else { Optional<ActionW> action = eventManager.getLeftMouseActionFromkeyEvent(e.getKeyCode(), e.getModifiers()); if (action.isPresent()) { eventManager.changeLeftMouseAction(action.get().cmd()); } else { eventManager.keyPressed(e); } } } private void drawPointer(Graphics2D g) { if (pointerType < 1) { return; } if ((pointerType & CENTER_POINTER) == CENTER_POINTER) { drawPointer(g, (getWidth() - 1) * 0.5, (getHeight() - 1) * 0.5); } if ((pointerType & HIGHLIGHTED_POINTER) == HIGHLIGHTED_POINTER && highlightedPosition.isHighlightedPosition()) { // Display the position on the center of the pixel (constant position even with a high zoom factor) drawPointer(g, modelToViewX(highlightedPosition.getX() + 0.5), modelToViewY(highlightedPosition.getY() + 0.5)); } } @Override public int getPointerType() { return pointerType; } @Override public void setPointerType(int pointerType) { this.pointerType = pointerType; } @Override public void addPointerType(int i) { this.pointerType |= i; } @Override public void resetPointerType(int i) { this.pointerType &= ~i; } @Override public Point2D getHighlightedPosition() { return highlightedPosition; } @Override public void drawPointer(Graphics2D g, Double x, Double y) { float[] dash = { 5.0f }; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.translate(x, y); g.setStroke(new BasicStroke(3.0f)); g.setPaint(pointerColor1); for (int i = 1; i < pointer.length; i++) { g.draw(pointer[i]); } g.setStroke(new BasicStroke(1.0f, 0, 0, 5.0f, dash, 0.0f)); g.setPaint(pointerColor2); for (int i = 1; i < pointer.length; i++) { g.draw(pointer[i]); } g.translate(-x, -y); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT); } protected void showPixelInfos(MouseEvent mouseevent) { if (infoLayer != null) { Point2D pModel = getImageCoordinatesFromMouse(mouseevent.getX(), mouseevent.getY()); Rectangle oldBound = infoLayer.getPixelInfoBound(); PixelInfo pixelInfo = getPixelInfo(new Point((int) Math.floor(pModel.getX()), (int) Math.floor(pModel.getY()))); oldBound.width = Math.max(oldBound.width, this.getGraphics().getFontMetrics(getLayerFont()) .stringWidth(Messages.getString("DefaultView2d.pix") + StringUtil.COLON_AND_SPACE + pixelInfo) + 4); //$NON-NLS-1$ infoLayer.setPixelInfo(pixelInfo); repaint(oldBound); } } @Override public void focusGained(FocusEvent e) { } @Override public void focusLost(FocusEvent e) { } // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class FocusHandler extends MouseActionAdapter { @Override public void mousePressed(MouseEvent evt) { ImageViewerPlugin<E> pane = eventManager.getSelectedView2dContainer(); if (Objects.isNull(pane)) { return; } ViewButton selectedButton = null; // Do select the view when pressing on a view button for (ViewButton b : getViewButtons()) { if (b.isVisible() && b.contains(evt.getPoint())) { selectedButton = b; break; } } if (evt.getClickCount() == 2 && selectedButton == null) { pane.maximizedSelectedImagePane(DefaultView2d.this, evt); return; } if (pane.isContainingView(DefaultView2d.this) && pane.getSelectedImagePane() != DefaultView2d.this) { // register all actions of the EventManager with this view waiting the focus gained in some cases is not // enough, because others mouseListeners are triggered before the focus event (that means before // registering the view in the EventManager) pane.setSelectedImagePane(DefaultView2d.this); } // request the focus even it is the same pane selected requestFocusInWindow(); // Do select the view when pressing on a view button if (selectedButton != null) { DefaultView2d.this.setCursor(DefaultView2d.DEFAULT_CURSOR); evt.consume(); selectedButton.showPopup(evt.getComponent(), evt.getX(), evt.getY()); return; } Optional<ActionW> action = eventManager.getMouseAction(evt.getModifiersEx()); DefaultView2d.this.setCursor(action.isPresent() ? action.get().getCursor() : DefaultView2d.DEFAULT_CURSOR); } @Override public void mouseMoved(MouseEvent e) { showPixelInfos(e); } @Override public void mouseReleased(MouseEvent e) { DefaultView2d.this.setCursor(DefaultView2d.DEFAULT_CURSOR); } } @Override public List<Action> getExportToClipboardAction() { List<Action> list = new ArrayList<>(); AbstractAction exportToClipboardAction = new DefaultAction(Messages.getString("DefaultView2d.clipboard"), event -> { //$NON-NLS-1$ final ViewTransferHandler imageTransferHandler = new ViewTransferHandler(); imageTransferHandler.exportToClipboard(DefaultView2d.this, Toolkit.getDefaultToolkit().getSystemClipboard(), TransferHandler.COPY); }); exportToClipboardAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK)); list.add(exportToClipboardAction); // TODO exclude big images? exportToClipboardAction = new DefaultAction(Messages.getString("DefaultView2d.clipboard_real"), event -> { //$NON-NLS-1$ final ImageTransferHandler imageTransferHandler = new ImageTransferHandler(); imageTransferHandler.exportToClipboard(DefaultView2d.this, Toolkit.getDefaultToolkit().getSystemClipboard(), TransferHandler.COPY); }); list.add(exportToClipboardAction); return list; } @Override public abstract void enableMouseAndKeyListener(MouseActions mouseActions); public static final AffineTransform getAffineTransform(MouseEvent mouseevent) { if (mouseevent != null && mouseevent.getSource() instanceof Image2DViewer) { return ((Image2DViewer) mouseevent.getSource()).getAffineTransform(); } return null; } @Override public void resetZoom() { ZoomType type = (ZoomType) actionsInView.get(ZOOM_TYPE_CMD); if (!ZoomType.CURRENT.equals(type)) { zoom(0.0); } } @Override public void resetPan() { center(); } @Override public void reset() { imageLayer.setEnableDispOperations(false); ImageViewerPlugin<E> pane = eventManager.getSelectedView2dContainer(); if (pane != null) { pane.resetMaximizedSelectedImagePane(this); } initActionWState(); imageLayer.fireOpEvent(new ImageOpEvent(ImageOpEvent.OpEvent.ResetDisplay, series, getImage(), null)); resetZoom(); resetPan(); imageLayer.setEnableDispOperations(true); eventManager.updateComponentsListener(this); } @Override public List<ViewButton> getViewButtons() { return viewButtons; } protected void copyGraphicsFromClipboard() { List<Graphic> graphs = DefaultView2d.GRAPHIC_CLIPBOARD.getGraphics(); if (graphs != null) { Rectangle2D area = getViewModel().getModelArea(); if (graphs.stream().anyMatch(g -> !g.getBounds(null).intersects(area))) { int option = JOptionPane.showConfirmDialog(this, "At least one graphic is outside the image.\n Do you want to continue?"); //$NON-NLS-1$ if (option != JOptionPane.YES_OPTION) { return; } } graphs.forEach(g -> AbstractGraphicModel.addGraphicToModel(this, g.copy())); // Repaint all because labels are not drawn repaint(); } } public static Cursor getNewCursor(int type) { return new Cursor(type); } public static Cursor getCustomCursor(String filename, String cursorName, int hotSpotX, int hotSpotY) { Toolkit defaultToolkit = Toolkit.getDefaultToolkit(); ImageIcon icon = new ImageIcon(GraphicUtil.class.getResource("/icon/cursor/" + filename)); //$NON-NLS-1$ Dimension bestCursorSize = defaultToolkit.getBestCursorSize(icon.getIconWidth(), icon.getIconHeight()); Point hotSpot = new Point((hotSpotX * bestCursorSize.width) / icon.getIconWidth(), (hotSpotY * bestCursorSize.height) / icon.getIconHeight()); return defaultToolkit.createCustomCursor(icon.getImage(), hotSpot, cursorName); } }