/******************************************************************************* * 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.dicom.viewer2d; import java.awt.Color; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.Point; import java.awt.Rectangle; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.FocusEvent; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.RenderedImage; import java.beans.PropertyChangeEvent; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.JSeparator; import javax.swing.KeyStroke; import javax.swing.TransferHandler; import javax.vecmath.Point3d; import javax.vecmath.Tuple3d; import javax.vecmath.Vector3d; import org.dcm4che3.data.Tag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.api.explorer.DataExplorerView; import org.weasis.core.api.explorer.model.DataExplorerModel; import org.weasis.core.api.explorer.model.TreeModel; 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.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.SimpleOpManager; import org.weasis.core.api.image.WindowOp; import org.weasis.core.api.image.ZoomOp; import org.weasis.core.api.image.util.ImageLayer; import org.weasis.core.api.media.data.MediaSeries; import org.weasis.core.api.media.data.MediaSeriesGroup; import org.weasis.core.api.media.data.Series; import org.weasis.core.api.media.data.SeriesThumbnail; import org.weasis.core.api.media.data.TagW; import org.weasis.core.api.service.AuditLog; import org.weasis.core.api.service.BundleTools; import org.weasis.core.api.util.StringUtil; import org.weasis.core.ui.dialog.MeasureDialog; import org.weasis.core.ui.docking.UIManager; import org.weasis.core.ui.editor.SeriesViewerFactory; import org.weasis.core.ui.editor.ViewerPluginBuilder; import org.weasis.core.ui.editor.image.CalibrationView; import org.weasis.core.ui.editor.image.DefaultView2d; import org.weasis.core.ui.editor.image.ImageViewerEventManager; import org.weasis.core.ui.editor.image.ImageViewerPlugin; import org.weasis.core.ui.editor.image.MouseActions; import org.weasis.core.ui.editor.image.PannerListener; import org.weasis.core.ui.editor.image.PixelInfo; import org.weasis.core.ui.editor.image.SynchData; import org.weasis.core.ui.editor.image.SynchData.Mode; import org.weasis.core.ui.editor.image.SynchEvent; import org.weasis.core.ui.editor.image.ViewButton; import org.weasis.core.ui.editor.image.ViewCanvas; import org.weasis.core.ui.editor.image.ViewerToolBar; import org.weasis.core.ui.model.AbstractGraphicModel; import org.weasis.core.ui.model.graphic.DragGraphic; import org.weasis.core.ui.model.graphic.Graphic; import org.weasis.core.ui.model.graphic.imp.area.PolygonGraphic; import org.weasis.core.ui.model.graphic.imp.area.RectangleGraphic; import org.weasis.core.ui.model.graphic.imp.line.LineGraphic; import org.weasis.core.ui.model.graphic.imp.line.LineWithGapGraphic; import org.weasis.core.ui.model.layer.GraphicLayer; import org.weasis.core.ui.model.layer.LayerType; import org.weasis.core.ui.model.utils.exceptions.InvalidShapeException; import org.weasis.core.ui.model.utils.imp.DefaultViewModel; import org.weasis.core.ui.util.ColorLayerUI; import org.weasis.core.ui.util.MouseEventDouble; import org.weasis.core.ui.util.TitleMenuItem; import org.weasis.core.ui.util.UriListFlavor; import org.weasis.dicom.codec.DicomEncapDocSeries; import org.weasis.dicom.codec.DicomImageElement; import org.weasis.dicom.codec.DicomSeries; import org.weasis.dicom.codec.DicomVideoSeries; import org.weasis.dicom.codec.KOSpecialElement; import org.weasis.dicom.codec.PRSpecialElement; import org.weasis.dicom.codec.PresentationStateReader; import org.weasis.dicom.codec.SortSeriesStack; import org.weasis.dicom.codec.TagD; import org.weasis.dicom.codec.display.OverlayOp; import org.weasis.dicom.codec.display.PresetWindowLevel; import org.weasis.dicom.codec.display.ShutterOp; import org.weasis.dicom.codec.display.WindowAndPresetsOp; import org.weasis.dicom.codec.geometry.GeometryOfSlice; import org.weasis.dicom.codec.geometry.ImageOrientation; import org.weasis.dicom.codec.geometry.ImageOrientation.Label; import org.weasis.dicom.codec.geometry.IntersectSlice; import org.weasis.dicom.codec.geometry.IntersectVolume; import org.weasis.dicom.codec.geometry.LocalizerPoster; import org.weasis.dicom.explorer.DicomExplorer; import org.weasis.dicom.explorer.DicomModel; import org.weasis.dicom.explorer.LoadLocalDicom; import org.weasis.dicom.explorer.MimeSystemAppFactory; import org.weasis.dicom.explorer.SeriesSelectionModel; import org.weasis.dicom.explorer.pr.PrGraphicUtil; import org.weasis.dicom.viewer2d.KOComponentFactory.KOViewButton; import org.weasis.dicom.viewer2d.KOComponentFactory.KOViewButton.eState; import org.weasis.dicom.viewer2d.mpr.MprView.SliceOrientation; public class View2d extends DefaultView2d<DicomImageElement> { private static final long serialVersionUID = 8334123827855840782L; private static final Logger LOGGER = LoggerFactory.getLogger(View2d.class); public static final ImageIcon KO_ICON = new ImageIcon(View2d.class.getResource("/icon/22x22/dcm-KO.png")); //$NON-NLS-1$ public static final ImageIcon PR_ICON = new ImageIcon(View2d.class.getResource("/icon/22x22/dcm-PR.png")); //$NON-NLS-1$ private final Dimension oldSize; private final ContextMenuHandler contextMenuHandler = new ContextMenuHandler(); protected final KOViewButton koStarButton; public View2d(ImageViewerEventManager<DicomImageElement> eventManager) { super(eventManager); SimpleOpManager manager = imageLayer.getDisplayOpManager(); manager.addImageOperationAction(new WindowAndPresetsOp()); manager.addImageOperationAction(new FilterOp()); manager.addImageOperationAction(new PseudoColorOp()); manager.addImageOperationAction(new ShutterOp()); manager.addImageOperationAction(new OverlayOp()); // Zoom and Rotation must be the last operations for the lens manager.addImageOperationAction(new ZoomOp()); manager.addImageOperationAction(new RotationOp()); manager.addImageOperationAction(new FlipOp()); infoLayer = new InfoLayer(this); oldSize = new Dimension(0, 0); // TODO should be a lazy instantiation getViewButtons().add(KOComponentFactory.buildKoSelectionButton(this)); koStarButton = KOComponentFactory.buildKoStarButton(this); koStarButton.setPosition(GridBagConstraints.NORTHEAST); getViewButtons().add(koStarButton); } @Override public void registerDefaultListeners() { super.registerDefaultListeners(); setTransferHandler(new SequenceHandler()); addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { View2d.this.componentResized(); } }); } private void componentResized() { Double currentZoom = (Double) actionsInView.get(ActionW.ZOOM.cmd()); /* * Negative value means a default value according to the zoom type (pixel size, best fit...). Set again to * default value to compute again the position. For instance, the image cannot be center aligned until the view * has been repaint once (because the size is null). */ if (currentZoom <= 0.0) { zoom(0.0); } if (panner != null) { panner.updateImageSize(); } if (lens != null) { int w = getWidth(); int h = getHeight(); if (w != 0 && h != 0) { Rectangle bound = lens.getBounds(); if (oldSize.width != 0 && oldSize.height != 0) { int centerx = bound.width / 2; int centery = bound.height / 2; bound.x = (bound.x + centerx) * w / oldSize.width - centerx; bound.y = (bound.y + centery) * h / oldSize.height - centery; lens.setLocation(bound.x, bound.y); } oldSize.width = w; oldSize.height = h; } lens.updateZoom(); } } @Override protected void initActionWState() { super.initActionWState(); actionsInView.put(ActionW.SORTSTACK.cmd(), SortSeriesStack.instanceNumber); // Preprocessing actionsInView.put(ActionW.CROP.cmd(), null); OpManager disOp = getDisplayOpManager(); disOp.setParamValue(ShutterOp.OP_NAME, ShutterOp.P_SHOW, true); disOp.setParamValue(OverlayOp.OP_NAME, OverlayOp.P_SHOW, true); disOp.setParamValue(WindowOp.OP_NAME, ActionW.IMAGE_PIX_PADDING.cmd(), true); disOp.setParamValue(WindowOp.OP_NAME, ActionW.DEFAULT_PRESET.cmd(), true); disOp.setParamValue(WindowOp.OP_NAME, ActionW.PRESET.cmd(), null); initKOActionWState(); } protected void initKOActionWState() { actionsInView.put(ActionW.KO_FILTER.cmd(), false); actionsInView.put(ActionW.KO_TOOGLE_STATE.cmd(), false); actionsInView.put(ActionW.KO_SELECTION.cmd(), ActionState.NoneLabel.NONE); } @Override public void propertyChange(PropertyChangeEvent evt) { super.propertyChange(evt); if (series == null) { return; } RenderedImage dispImage = imageLayer.getDisplayImage(); OpManager disOp = imageLayer.getDisplayOpManager(); final String name = evt.getPropertyName(); if (name.equals(ActionW.SYNCH.cmd())) { SynchEvent synch = (SynchEvent) evt.getNewValue(); SynchData synchData = (SynchData) actionsInView.get(ActionW.SYNCH_LINK.cmd()); boolean tile = synchData != null && SynchData.Mode.TILE.equals(synchData.getMode()); if (synchData != null && Mode.NONE.equals(synchData.getMode())) { return; } for (Entry<String, Object> entry : synch.getEvents().entrySet()) { final String command = entry.getKey(); final Object val = entry.getValue(); if (synchData != null && !synchData.isActionEnable(command)) { continue; } if (command.equals(ActionW.PRESET.cmd())) { actionsInView.put(ActionW.PRESET.cmd(), val); if (val instanceof PresetWindowLevel) { PresetWindowLevel preset = (PresetWindowLevel) val; DicomImageElement img = getImage(); ImageOpNode node = disOp.getNode(WindowOp.OP_NAME); if (node != null) { node.setParam(ActionW.WINDOW.cmd(), preset.getWindow()); node.setParam(ActionW.LEVEL.cmd(), preset.getLevel()); node.setParam(ActionW.LUT_SHAPE.cmd(), preset.getLutShape()); // When series synchronization, do not synch preset from other series if (img == null || !img.containsPreset(preset)) { List<PresetWindowLevel> presets = (List<PresetWindowLevel>) actionsInView.get(PRManager.PR_PRESETS); if (presets == null || !presets.contains(preset)) { preset = null; } } node.setParam(ActionW.PRESET.cmd(), preset); } imageLayer.updateDisplayOperations(); } } else if (command.equals(ActionW.DEFAULT_PRESET.cmd())) { disOp.setParamValue(WindowOp.OP_NAME, ActionW.DEFAULT_PRESET.cmd(), val); } else if (command.equals(ActionW.LUT_SHAPE.cmd())) { ImageOpNode node = disOp.getNode(WindowOp.OP_NAME); if (node != null) { node.setParam(ActionW.LUT_SHAPE.cmd(), val); } imageLayer.updateDisplayOperations(); } else if (command.equals(ActionW.SORTSTACK.cmd())) { actionsInView.put(ActionW.SORTSTACK.cmd(), val); sortStack(getCurrentSortComparator()); } else if (command.equals(ActionW.INVERSESTACK.cmd())) { actionsInView.put(ActionW.INVERSESTACK.cmd(), val); sortStack(getCurrentSortComparator()); } else if (command.equals(ActionW.KO_SELECTION.cmd())) { int frameIndex = tile ? JMVUtils.getNULLtoFalse(synch.getView().getActionValue(ActionW.KO_FILTER.cmd())) ? 0 : synch.getView().getFrameIndex() - synch.getView().getTileOffset() : -1; KOManager.updateKOFilter(this, val, (Boolean) (tile ? synch.getView().getActionValue(ActionW.KO_FILTER.cmd()) : null), frameIndex); } else if (command.equals(ActionW.KO_FILTER.cmd())) { int frameIndex = tile ? JMVUtils.getNULLtoFalse(val) ? 0 : synch.getView().getFrameIndex() - synch.getView().getTileOffset() : -1; KOManager.updateKOFilter(this, tile ? synch.getView().getActionValue(ActionW.KO_SELECTION.cmd()) : null, (Boolean) val, frameIndex); } else if (command.equals(ActionW.CROSSHAIR.cmd())) { if (series != null && val instanceof Point2D.Double) { Point2D.Double p = (Point2D.Double) val; GeometryOfSlice sliceGeometry = this.getImage().getDispSliceGeometry(); String fruid = TagD.getTagValue(series, Tag.FrameOfReferenceUID, String.class); if (sliceGeometry != null && fruid != null) { Point3d p3 = Double.isNaN(p.x) ? null : sliceGeometry.getPosition(p); ImageViewerPlugin<DicomImageElement> container = this.eventManager.getSelectedView2dContainer(); if (container != null) { List<ViewCanvas<DicomImageElement>> viewpanels = container.getImagePanels(); if (p3 != null) { for (ViewCanvas<DicomImageElement> v : viewpanels) { MediaSeries<DicomImageElement> s = v.getSeries(); if (s == null) { continue; } if (v instanceof View2d && fruid.equals(TagD.getTagValue(s, Tag.FrameOfReferenceUID))) { if (v != container.getSelectedImagePane()) { DicomImageElement imgToUpdate = v.getImage(); if (imgToUpdate != null) { GeometryOfSlice geometry = imgToUpdate.getDispSliceGeometry(); if (geometry != null) { Vector3d vn = geometry.getNormal(); // vn.absolute(); double location = p3.x * vn.x + p3.y * vn.y + p3.z * vn.z; DicomImageElement img = s.getNearestImage(location, 0, (Filter<DicomImageElement>) actionsInView .get(ActionW.FILTERED_SERIES.cmd()), v.getCurrentSortComparator()); if (img != null) { ((View2d) v).setImage(img); } } } } } } } for (ViewCanvas<DicomImageElement> v : viewpanels) { MediaSeries<DicomImageElement> s = v.getSeries(); if (s == null) { continue; } if (v instanceof View2d && fruid.equals(TagD.getTagValue(s, Tag.FrameOfReferenceUID)) && JMVUtils.getNULLtoTrue(actionsInView.get(LayerType.CROSSLINES.name()))) { ((View2d) v).computeCrosshair(p3); v.getJComponent().repaint(); } } } } } } } } else if (name.equals(ActionW.IMAGE_SHUTTER.cmd())) { if (disOp.setParamValue(ShutterOp.OP_NAME, ShutterOp.P_SHOW, evt.getNewValue())) { imageLayer.updateDisplayOperations(); } } else if (name.equals(ActionW.IMAGE_OVERLAY.cmd())) { if (disOp.setParamValue(OverlayOp.OP_NAME, OverlayOp.P_SHOW, evt.getNewValue())) { imageLayer.updateDisplayOperations(); } } if (lens != null) { if (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(name, evt.getNewValue()); lens.updateZoom(); } } } @Override public void reset() { super.reset(); DicomImageElement img = getImage(); if (img != null) { Object key = img.getKey(); List<PRSpecialElement> prList = DicomModel.getPrSpecialElements(series, TagD.getTagValue(img, Tag.SOPInstanceUID, String.class), key instanceof Integer ? (Integer) key + 1 : null); if (!prList.isEmpty()) { setPresentationState(prList.get(0), false); } } } void setPresentationState(Object val, boolean newImage) { Object old = actionsInView.get(ActionW.PR_STATE.cmd()); if (!newImage && Objects.equals(old, val)) { return; } actionsInView.put(ActionW.PR_STATE.cmd(), val); PresentationStateReader pr = val instanceof PRSpecialElement ? new PresentationStateReader((PRSpecialElement) val) : null; actionsInView.put(PresentationStateReader.TAG_PR_READER, pr); boolean spatialTransformation = actionsInView.get(ActionW.PREPROCESSING.cmd()) != null; actionsInView.put(ActionW.PREPROCESSING.cmd(), null); DicomImageElement m = getImage(); // Reset display parameter ((DefaultViewModel) getViewModel()).setEnableViewModelChangeListeners(false); imageLayer.setEnableDispOperations(false); imageLayer.fireOpEvent(new ImageOpEvent(ImageOpEvent.OpEvent.ResetDisplay, series, m, null)); boolean changePixConfig = JMVUtils.getNULLtoFalse(actionsInView.get(PRManager.TAG_CHANGE_PIX_CONFIG)); if (m != null) { // Restore the original image pixel size if (changePixConfig) { m.initPixelConfiguration(); ActionState spUnitAction = eventManager.getAction(ActionW.SPATIAL_UNIT); if (spUnitAction instanceof ComboItemListener) { ((ComboItemListener) spUnitAction).setSelectedItem(m.getPixelSpacingUnit()); } } deletePrLayers(); // Restore presets actionsInView.remove(PRManager.PR_PRESETS); // Restore zoom actionsInView.remove(PRManager.TAG_PR_ZOOM); // Reset crop updateCanvas(false); getImageLayer().setOffset(null); } // If no Presentation State use the current image if (pr == null) { // Keeps KO properties (series level) Object ko = actionsInView.get(ActionW.KO_SELECTION.cmd()); Object filter = actionsInView.get(ActionW.FILTERED_SERIES.cmd()); OpManager disOp = getDisplayOpManager(); Object preset = disOp.getParamValue(WindowOp.OP_NAME, ActionW.PRESET.cmd()); initActionWState(); setActionsInView(ActionW.KO_SELECTION.cmd(), ko); setActionsInView(ActionW.FILTERED_SERIES.cmd(), filter); // Set the image spatial unit setActionsInView(ActionW.SPATIAL_UNIT.cmd(), m.getPixelSpacingUnit()); disOp.setParamValue(WindowOp.OP_NAME, ActionW.PRESET.cmd(), preset); resetZoom(); resetPan(); } else { PRManager.applyPresentationState(this, pr, m); } Rectangle area = (Rectangle) actionsInView.get(ActionW.CROP.cmd()); if (area != null && !area.equals(getViewModel().getModelArea())) { ((DefaultViewModel) getViewModel()).adjustMinViewScaleFromImage(area.width, area.height); getViewModel().setModelArea(new Rectangle(0, 0, area.width, area.height)); getImageLayer().setOffset(new Point(area.x, area.y)); } SimpleOpManager opManager = (SimpleOpManager) actionsInView.get(ActionW.PREPROCESSING.cmd()); imageLayer.setPreprocessing(opManager); if(opManager == null && spatialTransformation){ // Reset preprocessing cache imageLayer.getDisplayOpManager().setFirstNode(imageLayer.getSourceRenderedImage()); } if (pr != null) { imageLayer.fireOpEvent(new ImageOpEvent(ImageOpEvent.OpEvent.ApplyPR, series, m, actionsInView)); ImageOpNode rotation = imageLayer.getDisplayOpManager().getNode(RotationOp.OP_NAME); if (rotation != null) { rotation.setParam(RotationOp.P_ROTATE, actionsInView.get(ActionW.ROTATION.cmd())); } ImageOpNode flip = imageLayer.getDisplayOpManager().getNode(FlipOp.OP_NAME); if (flip != null) { flip.setParam(FlipOp.P_FLIP, actionsInView.get(ActionW.FLIP.cmd())); } } Double zoom = (Double) actionsInView.get(PRManager.TAG_PR_ZOOM); // Special Cases: -200.0 => best fit, -100.0 => real world size if (zoom != null && MathUtil.isDifferent(zoom, -200.0) && MathUtil.isDifferent(zoom, -100.0)) { actionsInView.put(ViewCanvas.ZOOM_TYPE_CMD, ZoomType.CURRENT); zoom(zoom); } else if (zoom != null) { actionsInView.put(ViewCanvas.ZOOM_TYPE_CMD, MathUtil.isEqual(zoom, -100.0) ? ZoomType.REAL : ZoomType.BEST_FIT); zoom(0.0); } else if (changePixConfig || spatialTransformation) { zoom(0.0); } ((DefaultViewModel) getViewModel()).setEnableViewModelChangeListeners(true); imageLayer.setEnableDispOperations(true); eventManager.updateComponentsListener(this); } public void updateKOButtonVisibleState() { Collection<KOSpecialElement> koElements = DicomModel.getKoSpecialElements(getSeries()); boolean koElementExist = koElements != null && !koElements.isEmpty(); // TODO try a given parameter so it wouldn't have to be computed again boolean needToRepaint = false; for (ViewButton vb : getViewButtons()) { if (vb != null && vb.getIcon() == View2d.KO_ICON) { if (vb.isVisible() != koElementExist) { vb.setVisible(koElementExist); // repaint(getExtendedActionsBound()); needToRepaint = true; } break; } } if (koStarButton.isVisible() != koElementExist) { koStarButton.setVisible(koElementExist); needToRepaint = true; } if (koElementExist) { needToRepaint = updateKOselectedState(getImage()); } if (needToRepaint) { repaint(); } } /** * @param newImg * @param img * , newImg * @return true if the state has changed and if the view or at least the KO button need to be repaint */ protected boolean updateKOselectedState(DicomImageElement img) { eState previousState = koStarButton.getState(); // evaluate koSelection status for every Image change KOViewButton.eState newSelectionState = eState.UNSELECTED; Object selectedKO = getActionValue(ActionW.KO_SELECTION.cmd()); if (img != null) { String sopInstanceUID = TagD.getTagValue(img, Tag.SOPInstanceUID, String.class); String seriesInstanceUID = TagD.getTagValue(img, Tag.SeriesInstanceUID, String.class); if (sopInstanceUID != null && seriesInstanceUID != null) { if (selectedKO instanceof KOSpecialElement) { KOSpecialElement koElement = (KOSpecialElement) selectedKO; if (koElement.containsSopInstanceUIDReference(seriesInstanceUID, sopInstanceUID)) { newSelectionState = eState.SELECTED; } } else { Collection<KOSpecialElement> koElements = DicomModel.getKoSpecialElements(getSeries()); if (koElements != null) { for (KOSpecialElement koElement : koElements) { if (koElement.containsSopInstanceUIDReference(seriesInstanceUID, sopInstanceUID)) { newSelectionState = eState.EXIST; break; } } } } } } koStarButton.setState(newSelectionState); Boolean selected = koStarButton.getState().equals(eState.SELECTED) ? true : false; actionsInView.put(ActionW.KO_TOOGLE_STATE.cmd(), selected); return previousState != newSelectionState; } @Override public void setSeries(MediaSeries<DicomImageElement> series, DicomImageElement selectedDicom) { super.setSeries(series, selectedDicom); // TODO // JFrame frame = new JFrame(); // frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // JPanel pane = new JPanel(); // layeredPane.setPreferredSize(new Dimension(200, 200)); // pane.remove(layeredPane); // layeredPane.removeAll(); // panner.setSize(200, 200); // layeredPane.add(panner, JLayeredPane.DEFAULT_LAYER); // pane.add(layeredPane); // panner.setBounds(0, 0, 200, 200); // pane.setBounds(0, 0, 200, 200); // frame.add(pane); // frame.pack(); // frame.setVisible(true); if (series != null) { AuditLog.LOGGER.info("open:series nb:{} modality:{}", series.getSeriesNumber(), //$NON-NLS-1$ TagD.getTagValue(series, Tag.Modality)); } updateKOButtonVisibleState(); } @Override protected void setImage(DicomImageElement img) { boolean newImg = img != null && !img.equals(imageLayer.getSourceImage()); if (newImg) { deletePrLayers(); PrGraphicUtil.applyPresentationModel(img); } super.setImage(img); updatePrButtonState(img, newImg); if (newImg) { updateKOselectedState(img); } } private void deletePrLayers() { // Delete previous PR Layers List<GraphicLayer> dcmLayers = (List<GraphicLayer>) actionsInView.get(PRManager.TAG_DICOM_LAYERS); if (dcmLayers != null) { PRManager.deleteDicomLayers(dcmLayers, graphicManager); actionsInView.remove(PRManager.TAG_DICOM_LAYERS); } } void updatePR() { DicomImageElement img = imageLayer.getSourceImage(); if (img != null) { updatePrButtonState(img, true); } } private synchronized void updatePrButtonState(DicomImageElement img, boolean newImg) { if (newImg) { Object oldPR = getActionValue(ActionW.PR_STATE.cmd()); ViewButton prButton = PRManager.buildPrSelection(this, series, img); getViewButtons().removeIf(b -> b == null || b.getIcon() == View2d.PR_ICON); if (prButton != null) { getViewButtons().add(prButton); } else if (oldPR instanceof PRSpecialElement) { setPresentationState(null, newImg); actionsInView.put(ActionW.PR_STATE.cmd(), oldPR); } else if (ActionState.NoneLabel.NONE.equals(oldPR)) { // No persistence for NONE actionsInView.put(ActionW.PR_STATE.cmd(), null); } } } protected void sortStack(Comparator<DicomImageElement> sortComparator) { if (sortComparator != null) { // Only refresh UI components, Fix WEA-222 eventManager.updateComponentsListener(this); } } @Override protected void computeCrosslines(double location) { DicomImageElement image = this.getImage(); if (image != null) { GeometryOfSlice sliceGeometry = image.getDispSliceGeometry(); if (sliceGeometry != null) { ViewCanvas<DicomImageElement> view2DPane = eventManager.getSelectedViewPane(); MediaSeries<DicomImageElement> selSeries = view2DPane == null ? null : view2DPane.getSeries(); if (selSeries != null) { // Get the current image of the selected Series DicomImageElement selImage = view2DPane.getImage(); // Get the first and the last image of the selected Series according to Slice Location DicomImageElement firstImage = null; DicomImageElement lastImage = null; double min = Double.MAX_VALUE; double max = -Double.MAX_VALUE; final Iterable<DicomImageElement> list = selSeries.getMedias( (Filter<DicomImageElement>) view2DPane.getActionValue(ActionW.FILTERED_SERIES.cmd()), getCurrentSortComparator()); synchronized (selSeries) { for (DicomImageElement dcm : list) { double[] loc = (double[]) dcm.getTagValue(TagW.SlicePosition); if (loc != null) { double position = loc[0] + loc[1] + loc[2]; if (min > position) { min = position; firstImage = dcm; } if (max < position) { max = position; lastImage = dcm; } } } } GraphicLayer layer = AbstractGraphicModel.getOrBuildLayer(this, LayerType.CROSSLINES); // IntersectSlice: display a line representing the center of the slice IntersectSlice slice = new IntersectSlice(sliceGeometry); if (firstImage != null && firstImage != lastImage) { addCrossline(firstImage, layer, slice, false); } if (lastImage != null && firstImage != lastImage) { addCrossline(lastImage, layer, slice, false); } if (selImage != null) { // IntersectVolume: display a rectangle to show the slice thickness addCrossline(selImage, layer, new IntersectVolume(sliceGeometry), true); } repaint(); } } } } protected void addCrossline(DicomImageElement selImage, GraphicLayer layer, LocalizerPoster localizer, boolean center) { GeometryOfSlice sliceGeometry = selImage.getDispSliceGeometry(); if (sliceGeometry != null) { List<Point2D.Double> pts = localizer.getOutlineOnLocalizerForThisGeometry(sliceGeometry); if (pts != null && !pts.isEmpty()) { Color color = center ? Color.blue : Color.cyan; try { Graphic graphic; if (pts.size() == 2) { graphic = new LineGraphic().buildGraphic(pts); } else { graphic = new PolygonGraphic().buildGraphic(pts); } graphic.setPaint(color); graphic.setLabelVisible(Boolean.FALSE); graphic.setLayer(layer); graphicManager.addGraphic(graphic); } catch (InvalidShapeException e) { LOGGER.error("Building crossline", e); //$NON-NLS-1$ } } } } @Override public synchronized void enableMouseAndKeyListener(MouseActions actions) { disableMouseAndKeyListener(); iniDefaultMouseListener(); iniDefaultKeyListener(); // Set the butonMask to 0 of all the actions resetMouseAdapter(); this.setCursor(DefaultView2d.DEFAULT_CURSOR); addMouseAdapter(actions.getLeft(), InputEvent.BUTTON1_DOWN_MASK); // left mouse button if (actions.getMiddle().equals(actions.getLeft())) { // If mouse action is already registered, only add the modifier mask addModifierMask(actions.getMiddle(), InputEvent.BUTTON2_DOWN_MASK); } else { addMouseAdapter(actions.getMiddle(), InputEvent.BUTTON2_DOWN_MASK);// middle mouse button } if (actions.getRight().equals(actions.getLeft()) || actions.getRight().equals(actions.getMiddle())) { // If mouse action is already registered, only add the modifier mask addModifierMask(actions.getRight(), InputEvent.BUTTON3_DOWN_MASK); } else { addMouseAdapter(actions.getRight(), InputEvent.BUTTON3_DOWN_MASK); // right mouse button } this.addMouseWheelListener(getMouseAdapter(actions.getWheel())); if (lens != null) { lens.enableMouseListener(); } } private void addMouseAdapter(String actionName, int buttonMask) { MouseActionAdapter adapter = getMouseAdapter(actionName); if (adapter == null) { return; } adapter.setButtonMaskEx(adapter.getButtonMaskEx() | buttonMask); if (adapter == graphicMouseHandler) { this.addKeyListener(drawingsKeyListeners); } else if (adapter instanceof PannerListener) { ((PannerListener) adapter).reset(); this.addKeyListener((PannerListener) adapter); } if (actionName.equals(ActionW.WINLEVEL.cmd())) { // For window/level action set window action on x axis MouseActionAdapter win = getAction(ActionW.WINDOW); if (win != null) { win.setButtonMaskEx(win.getButtonMaskEx() | buttonMask); win.setMoveOnX(true); this.addMouseListener(win); this.addMouseMotionListener(win); } // set level action with inverse progression (move the cursor down will decrease the values) adapter.setInverse(true); } else if (actionName.equals(ActionW.WINDOW.cmd())) { adapter.setMoveOnX(false); } else if (actionName.equals(ActionW.LEVEL.cmd())) { adapter.setInverse(true); } this.addMouseListener(adapter); this.addMouseMotionListener(adapter); } private void addModifierMask(String action, int mask) { MouseActionAdapter adapter = getMouseAdapter(action); if (adapter != null) { adapter.setButtonMaskEx(adapter.getButtonMaskEx() | mask); if (ActionW.WINLEVEL.cmd().equals(action)) { MouseActionAdapter win = getMouseAdapter(ActionW.WINDOW.cmd()); if (win != null) { win.setButtonMaskEx(win.getButtonMaskEx() | mask); } } } } protected MouseActionAdapter getMouseAdapter(String command) { if (command.equals(ActionW.CONTEXTMENU.cmd())) { return contextMenuHandler; } else if (command.equals(ActionW.WINLEVEL.cmd())) { return getAction(ActionW.LEVEL); } Optional<ActionW> actionKey = eventManager.getActionKey(command); if (!actionKey.isPresent()) { return null; } if (actionKey.get().isDrawingAction()) { return graphicMouseHandler; } return eventManager.getAction(actionKey.get(), MouseActionAdapter.class).orElse(null); } public void computeCrosshair(Point3d p3) { DicomImageElement image = this.getImage(); if (image != null) { graphicManager.deleteByLayerType(LayerType.CROSSLINES); GraphicLayer layer = AbstractGraphicModel.getOrBuildLayer(this, LayerType.CROSSLINES); GeometryOfSlice sliceGeometry = image.getDispSliceGeometry(); if (sliceGeometry != null) { SliceOrientation sliceOrientation = this.getSliceOrientation(); if (sliceOrientation != null && p3 != null) { Point2D p = sliceGeometry.getImagePosition(p3); Tuple3d dimensions = sliceGeometry.getDimensions(); boolean axial = SliceOrientation.AXIAL.equals(sliceOrientation); Point2D centerPt = new Point2D.Double(p.getX(), p.getY()); List<Point2D.Double> pts = new ArrayList<>(); pts.add(new Point2D.Double(p.getX(), 0.0)); pts.add(new Point2D.Double(p.getX(), dimensions.x)); boolean sagittal = SliceOrientation.SAGITTAL.equals(sliceOrientation); Color color1 = sagittal ? Color.GREEN : Color.BLUE; addCrosshairLine(layer, pts, color1, centerPt); List<Point2D.Double> pts2 = new ArrayList<>(); Color color2 = axial ? Color.GREEN : Color.RED; pts2.add(new Point2D.Double(0.0, p.getY())); pts2.add(new Point2D.Double(dimensions.y, p.getY())); addCrosshairLine(layer, pts2, color2, centerPt); RenderedImage dispImg = image.getImage(); if (dispImg != null) { Rectangle2D rect = new Rectangle2D.Double(dispImg.getMinX() * image.getRescaleX(), dispImg.getMinY() * image.getRescaleY(), dispImg.getWidth() * image.getRescaleX(), dispImg.getHeight() * image.getRescaleY()); addRectangle(layer, rect, axial ? Color.RED : sagittal ? Color.BLUE : Color.GREEN); } } } } } protected void addCrosshairLine(GraphicLayer layer, List<Point2D.Double> pts, Color color, Point2D center) { if (pts != null && !pts.isEmpty()) { try { Graphic graphic; if (pts.size() == 2) { LineWithGapGraphic line = new LineWithGapGraphic(); line.setCenterGap(center); line.setGapSize(75); graphic = line.buildGraphic(pts); } else { graphic = new PolygonGraphic().buildGraphic(pts); } graphic.setPaint(color); graphic.setLabelVisible(Boolean.FALSE); graphic.setLayer(layer); graphicManager.addGraphic(graphic); } catch (InvalidShapeException e) { LOGGER.error("Add crosshair line", e); //$NON-NLS-1$ } } } protected void addRectangle(GraphicLayer layer, Rectangle2D rect, Color color) { if (rect != null && layer != null) { try { Graphic graphic = new RectangleGraphic().buildGraphic(rect); graphic.setPaint(color); graphic.setLabelVisible(Boolean.FALSE); graphic.setFilled(Boolean.FALSE); graphic.setLayer(layer); graphicManager.addGraphic(graphic); } catch (InvalidShapeException e) { LOGGER.error("Add rectangle", e); //$NON-NLS-1$ } } } public SliceOrientation getSliceOrientation() { SliceOrientation sliceOrientation = null; MediaSeries<DicomImageElement> s = getSeries(); if (s != null) { Object img = s.getMedia(MediaSeries.MEDIA_POSITION.MIDDLE, null, null); if (img instanceof DicomImageElement) { double[] v = TagD.getTagValue((DicomImageElement) img, Tag.ImageOrientationPatient, double[].class); if (v != null && v.length == 6) { Label orientation = ImageOrientation.makeImageOrientationLabelFromImageOrientationPatient(v[0], v[1], v[2], v[3], v[4], v[5]); if (ImageOrientation.Label.AXIAL.equals(orientation)) { sliceOrientation = SliceOrientation.AXIAL; } else if (ImageOrientation.Label.CORONAL.equals(orientation)) { sliceOrientation = SliceOrientation.CORONAL; } else if (ImageOrientation.Label.SAGITTAL.equals(orientation)) { sliceOrientation = SliceOrientation.SAGITTAL; } } } } return sliceOrientation; } protected void resetMouseAdapter() { for (ActionState adapter : eventManager.getAllActionValues()) { if (adapter instanceof MouseActionAdapter) { ((MouseActionAdapter) adapter).setButtonMaskEx(0); } } // reset context menu that is a field of this instance contextMenuHandler.setButtonMaskEx(0); graphicMouseHandler.setButtonMaskEx(0); } protected MouseActionAdapter getAction(ActionW action) { ActionState a = eventManager.getAction(action); if (a instanceof MouseActionAdapter) { return (MouseActionAdapter) a; } return null; } @Override protected void fillPixelInfo(final PixelInfo pixelInfo, final DicomImageElement imageElement, final double[] c) { if (c != null && c.length >= 1) { boolean pixelPadding = JMVUtils .getNULLtoTrue(getDisplayOpManager().getParamValue(WindowOp.OP_NAME, ActionW.IMAGE_PIX_PADDING.cmd())); PresentationStateReader prReader = (PresentationStateReader) getActionValue(PresentationStateReader.TAG_PR_READER); for (int i = 0; i < c.length; i++) { c[i] = imageElement.pixel2mLUT(c[i], prReader, pixelPadding); } pixelInfo.setValues(c); } } @Override public void handleLayerChanged(ImageLayer layer) { repaint(); } @Override public void focusGained(FocusEvent e) { if (!e.isTemporary()) { ImageViewerPlugin<DicomImageElement> pane = eventManager.getSelectedView2dContainer(); if (pane != null && pane.isContainingView(this)) { pane.setSelectedImagePaneFromFocus(this); } } } protected JPopupMenu buildGraphicContextMenu(final MouseEvent evt, final List<Graphic> selected) { if (selected != null) { final JPopupMenu popupMenu = new JPopupMenu(); TitleMenuItem itemTitle = new TitleMenuItem(Messages.getString("View2d.selection"), popupMenu.getInsets()); //$NON-NLS-1$ popupMenu.add(itemTitle); popupMenu.addSeparator(); boolean graphicComplete = true; if (selected.size() == 1) { final Graphic graph = selected.get(0); if (graph instanceof DragGraphic) { final DragGraphic absgraph = (DragGraphic) graph; if (!absgraph.isGraphicComplete()) { graphicComplete = false; } if (absgraph.getVariablePointsNumber()) { if (graphicComplete) { /* * Convert mouse event point to real image coordinate point (without geometric * transformation) */ final MouseEventDouble mouseEvt = new MouseEventDouble(View2d.this, MouseEvent.MOUSE_RELEASED, evt.getWhen(), 16, 0, 0, 0, 0, 1, true, 1); mouseEvt.setSource(View2d.this); mouseEvt.setImageCoordinates(getImageCoordinatesFromMouse(evt.getX(), evt.getY())); final int ptIndex = absgraph.getHandlePointIndex(mouseEvt); if (ptIndex >= 0) { JMenuItem menuItem = new JMenuItem(Messages.getString("View2d.rmv_pt")); //$NON-NLS-1$ menuItem.addActionListener(e -> absgraph.removeHandlePoint(ptIndex, mouseEvt)); popupMenu.add(menuItem); menuItem = new JMenuItem(Messages.getString("View2d.draw_pt")); //$NON-NLS-1$ menuItem.addActionListener(e -> { absgraph.forceToAddPoints(ptIndex); MouseEventDouble evt2 = new MouseEventDouble(View2d.this, MouseEvent.MOUSE_PRESSED, evt.getWhen(), 16, evt.getX(), evt.getY(), evt.getXOnScreen(), evt.getYOnScreen(), 1, true, 1); graphicMouseHandler.mousePressed(evt2); }); popupMenu.add(menuItem); popupMenu.add(new JSeparator()); } } else if (graphicMouseHandler.getDragSequence() != null && absgraph.getPtsNumber() == Graphic.UNDEFINED) { final JMenuItem item2 = new JMenuItem(Messages.getString("View2d.stop_draw")); //$NON-NLS-1$ item2.addActionListener(e -> { MouseEventDouble event = new MouseEventDouble(View2d.this, 0, 0, 16, 0, 0, 0, 0, 2, true, 1); graphicMouseHandler.getDragSequence().completeDrag(event); graphicMouseHandler.mouseReleased(event); }); popupMenu.add(item2); popupMenu.add(new JSeparator()); } } } } if (graphicComplete) { JMenuItem menuItem = new JMenuItem(Messages.getString("View2d.delete_sel")); //$NON-NLS-1$ menuItem .addActionListener(e -> View2d.this.getGraphicManager().deleteSelectedGraphics(View2d.this, true)); popupMenu.add(menuItem); menuItem = new JMenuItem(Messages.getString("View2d.cut")); //$NON-NLS-1$ menuItem.addActionListener(e -> { DefaultView2d.GRAPHIC_CLIPBOARD.setGraphics(selected); View2d.this.getGraphicManager().deleteSelectedGraphics(View2d.this, false); }); popupMenu.add(menuItem); menuItem = new JMenuItem(Messages.getString("View2d.copy")); //$NON-NLS-1$ menuItem.addActionListener(e -> DefaultView2d.GRAPHIC_CLIPBOARD.setGraphics(selected)); popupMenu.add(menuItem); popupMenu.add(new JSeparator()); } // TODO separate AbstractDragGraphic and ClassGraphic for properties final ArrayList<DragGraphic> list = new ArrayList<>(); for (Graphic graphic : selected) { if (graphic instanceof DragGraphic) { list.add((DragGraphic) graphic); } } if (selected.size() == 1) { final Graphic graph = selected.get(0); JMenuItem item = new JMenuItem(Messages.getString("View2d.to_front")); //$NON-NLS-1$ item.addActionListener(e -> graph.toFront()); popupMenu.add(item); item = new JMenuItem(Messages.getString("View2d.to_back")); //$NON-NLS-1$ item.addActionListener(e -> graph.toBack()); popupMenu.add(item); popupMenu.add(new JSeparator()); if (graphicComplete && graph instanceof LineGraphic) { final JMenuItem calibMenu = new JMenuItem(Messages.getString("View2d.chg_calib")); //$NON-NLS-1$ calibMenu.addActionListener(e -> { String title = Messages.getString("View2d.clibration"); //$NON-NLS-1$ CalibrationView calibrationDialog = new CalibrationView((LineGraphic) graph, View2d.this, true); ColorLayerUI layer = ColorLayerUI.createTransparentLayerUI(View2d.this); int res = JOptionPane.showConfirmDialog(ColorLayerUI.getContentPane(layer), calibrationDialog, title, JOptionPane.OK_CANCEL_OPTION); if (layer != null) { layer.hideUI(); } if (res == JOptionPane.OK_OPTION) { calibrationDialog.applyNewCalibration(); } }); popupMenu.add(calibMenu); popupMenu.add(new JSeparator()); } } if (!list.isEmpty()) { JMenuItem properties = new JMenuItem(Messages.getString("View2d.draw_prop")); //$NON-NLS-1$ properties.addActionListener(e -> { ColorLayerUI layer = ColorLayerUI.createTransparentLayerUI(View2d.this); JDialog dialog = new MeasureDialog(View2d.this, list); ColorLayerUI.showCenterScreen(dialog, layer); }); popupMenu.add(properties); } return popupMenu; } return null; } protected JPopupMenu buildContexMenu(final MouseEvent evt) { JPopupMenu popupMenu = new JPopupMenu(); TitleMenuItem itemTitle = new TitleMenuItem(Messages.getString("View2d.left_mouse") + StringUtil.COLON, popupMenu.getInsets()); //$NON-NLS-1$ popupMenu.add(itemTitle); popupMenu.setLabel(MouseActions.LEFT); String action = eventManager.getMouseActions().getLeft(); ButtonGroup groupButtons = new ButtonGroup(); int count = popupMenu.getComponentCount(); ImageViewerPlugin<DicomImageElement> view = eventManager.getSelectedView2dContainer(); if (view != null) { final ViewerToolBar<?> toolBar = view.getViewerToolBar(); if (toolBar != null) { ActionListener leftButtonAction = event -> { if (event.getSource() instanceof JRadioButtonMenuItem) { JRadioButtonMenuItem item = (JRadioButtonMenuItem) event.getSource(); toolBar.changeButtonState(MouseActions.LEFT, item.getActionCommand()); } }; List<ActionW> actionsButtons = ViewerToolBar.actionsButtons; synchronized (actionsButtons) { for (int i = 0; i < actionsButtons.size(); i++) { ActionW b = actionsButtons.get(i); if (eventManager.isActionRegistered(b)) { JRadioButtonMenuItem radio = new JRadioButtonMenuItem(b.getTitle(), b.getIcon(), b.cmd().equals(action)); radio.setActionCommand(b.cmd()); radio.setAccelerator(KeyStroke.getKeyStroke(b.getKeyCode(), b.getModifier())); // Trigger the selected mouse action radio.addActionListener(toolBar); // Update the state of the button in the toolbar radio.addActionListener(leftButtonAction); popupMenu.add(radio); groupButtons.add(radio); } } } } } if (count < popupMenu.getComponentCount()) { popupMenu.add(new JSeparator()); count = popupMenu.getComponentCount(); } if (DefaultView2d.GRAPHIC_CLIPBOARD.hasGraphics()) { JMenuItem menuItem = new JMenuItem(Messages.getString("View2d.paste_draw")); //$NON-NLS-1$ menuItem.addActionListener(e -> copyGraphicsFromClipboard()); popupMenu.add(menuItem); } if (count < popupMenu.getComponentCount()) { popupMenu.add(new JSeparator()); count = popupMenu.getComponentCount(); } if (eventManager instanceof EventManager) { EventManager manager = (EventManager) eventManager; JMVUtils.addItemToMenu(popupMenu, manager.getPresetMenu("weasis.contextmenu.presets")); //$NON-NLS-1$ JMVUtils.addItemToMenu(popupMenu, manager.getLutShapeMenu("weasis.contextmenu.lutShape")); //$NON-NLS-1$ JMVUtils.addItemToMenu(popupMenu, manager.getLutMenu("weasis.contextmenu.lut")); //$NON-NLS-1$ JMVUtils.addItemToMenu(popupMenu, manager.getLutInverseMenu("weasis.contextmenu.invertLut")); //$NON-NLS-1$ JMVUtils.addItemToMenu(popupMenu, manager.getFilterMenu("weasis.contextmenu.filter")); //$NON-NLS-1$ if (count < popupMenu.getComponentCount()) { popupMenu.add(new JSeparator()); count = popupMenu.getComponentCount(); } JMVUtils.addItemToMenu(popupMenu, manager.getZoomMenu("weasis.contextmenu.zoom")); //$NON-NLS-1$ JMVUtils.addItemToMenu(popupMenu, manager.getOrientationMenu("weasis.contextmenu.orientation")); //$NON-NLS-1$ JMVUtils.addItemToMenu(popupMenu, manager.getSortStackMenu("weasis.contextmenu.sortstack")); //$NON-NLS-1$ if (count < popupMenu.getComponentCount()) { popupMenu.add(new JSeparator()); } JMVUtils.addItemToMenu(popupMenu, manager.getResetMenu("weasis.contextmenu.reset")); //$NON-NLS-1$ } if (BundleTools.SYSTEM_PREFERENCES.getBooleanProperty("weasis.contextmenu.close", true)) { //$NON-NLS-1$ JMenuItem close = new JMenuItem(Messages.getString("View2d.close")); //$NON-NLS-1$ close.addActionListener(e -> View2d.this.setSeries(null, null)); popupMenu.add(close); } return popupMenu; } class ContextMenuHandler extends MouseActionAdapter { @Override public void mousePressed(final MouseEvent evt) { showPopup(evt); } @Override public void mouseReleased(final MouseEvent evt) { showPopup(evt); } private void showPopup(final MouseEvent evt) { // Context menu if ((evt.getModifiersEx() & getButtonMaskEx()) != 0) { JPopupMenu popupMenu = null; final List<Graphic> selected = View2d.this.getGraphicManager().getSelectedGraphics(); if (!selected.isEmpty() && isDrawActionActive()) { popupMenu = View2d.this.buildGraphicContextMenu(evt, selected); } else if (View2d.this.getSourceImage() != null) { popupMenu = View2d.this.buildContexMenu(evt); } if (popupMenu != null) { popupMenu.show(evt.getComponent(), evt.getX(), evt.getY()); } } } } private class SequenceHandler extends TransferHandler { public SequenceHandler() { super("series"); //$NON-NLS-1$ } @Override public Transferable createTransferable(JComponent comp) { if (comp instanceof SeriesThumbnail) { MediaSeries<?> t = ((SeriesThumbnail) comp).getSeries(); if (t instanceof Series) { return t; } } return null; } @Override public boolean canImport(TransferSupport support) { if (!support.isDrop()) { return false; } if (support.isDataFlavorSupported(Series.sequenceDataFlavor) || support.isDataFlavorSupported(DataFlavor.javaFileListFlavor) || support.isDataFlavorSupported(UriListFlavor.flavor)) { return true; } return false; } @Override public boolean importData(TransferSupport support) { if (!canImport(support)) { return false; } Transferable transferable = support.getTransferable(); List<File> files = null; // Not supported by some OS if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { try { files = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor); } catch (Exception e) { LOGGER.error("Get dragable files", e); //$NON-NLS-1$ } return dropDicomFiles(files); } // When dragging a file or group of files // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4899516 else if (support.isDataFlavorSupported(UriListFlavor.flavor)) { try { // Files with spaces in the filename trigger an error // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6936006 String val = (String) transferable.getTransferData(UriListFlavor.flavor); files = UriListFlavor.textURIListToFileList(val); } catch (Exception e) { LOGGER.error("Get dragable URIs", e); //$NON-NLS-1$ } return dropDicomFiles(files); } DataExplorerView dicomView = UIManager.getExplorerplugin(DicomExplorer.NAME); DataExplorerModel model = null; SeriesSelectionModel selList = null; if (dicomView != null) { selList = ((DicomExplorer) dicomView).getSelectionList(); } View2dContainer selPlugin = (View2dContainer) UIManager.VIEWER_PLUGINS.stream() .filter(p -> p instanceof View2dContainer && ((View2dContainer) p).isContainingView(View2d.this)) .findFirst().get(); Series seq; try { seq = (Series) transferable.getTransferData(Series.sequenceDataFlavor); if (seq == null) { return false; } model = (DataExplorerModel) seq.getTagValue(TagW.ExplorerModel); if (seq instanceof DicomSeries && model instanceof TreeModel) { TreeModel treeModel = (TreeModel) model; if (selList != null) { selList.setOpenningSeries(true); } MediaSeriesGroup p1 = treeModel.getParent(seq, model.getTreeModelNodeForNewPlugin()); MediaSeriesGroup p2 = null; if (p1 == null) { return false; } if (p1.equals(selPlugin.getGroupID())) { p2 = p1; } if (!p1.equals(p2)) { SeriesViewerFactory plugin = UIManager.getViewerFactory(selPlugin); if (plugin != null && !(plugin instanceof MimeSystemAppFactory)) { ViewerPluginBuilder.openSequenceInPlugin(plugin, seq, model, true, true); } return false; } } else if (seq instanceof DicomEncapDocSeries || seq instanceof DicomVideoSeries) { ViewerPluginBuilder.openSequenceInDefaultPlugin(seq, model, true, true); return true; } else { // Not a DICOM Series return false; } } catch (Exception e) { LOGGER.error("Get dragable series", e); //$NON-NLS-1$ return false; } finally { if (selList != null) { selList.setOpenningSeries(false); } } if (selList != null) { selList.setOpenningSeries(true); } if (SynchData.Mode.TILE.equals(selPlugin.getSynchView().getSynchData().getMode())) { selPlugin.addSeries(seq); if (selList != null) { selList.setOpenningSeries(false); } return true; } setSeries(seq); // Getting the focus has a delay and so it will trigger the view selection later if (selPlugin.isContainingView(View2d.this)) { selPlugin.setSelectedImagePaneFromFocus(View2d.this); } if (selList != null) { selList.setOpenningSeries(false); } return true; } private boolean dropDicomFiles(List<File> files) { if (files != null) { DataExplorerView dicomView = UIManager.getExplorerplugin(DicomExplorer.NAME); if (dicomView == null) { return false; } DicomModel model = (DicomModel) dicomView.getDataExplorerModel(); LoadLocalDicom dicom = new LoadLocalDicom(files.stream().toArray(File[]::new), true, model); DicomModel.LOADING_EXECUTOR.execute(dicom); return true; } return false; } } }