/******************************************************************************* * 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.explorer; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.border.EmptyBorder; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultHighlighter; import javax.swing.text.Document; import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; import javax.swing.text.StyledDocument; import javax.swing.text.html.HTMLEditorKit; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.ElementDictionary; import org.dcm4che3.data.Sequence; import org.dcm4che3.data.VR; import org.dcm4che3.imageio.plugins.dcm.DicomMetaData; import org.dcm4che3.util.TagUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.api.gui.util.JMVUtils; import org.weasis.core.api.media.data.ImageElement; import org.weasis.core.api.media.data.MediaElement; import org.weasis.core.api.media.data.MediaReader; 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.TagReadable; import org.weasis.core.api.media.data.TagUtil; import org.weasis.core.api.media.data.TagView; import org.weasis.core.api.media.data.TagW; import org.weasis.core.api.service.AuditLog; import org.weasis.core.api.util.StringUtil; import org.weasis.core.ui.editor.SeriesViewerEvent; import org.weasis.core.ui.editor.SeriesViewerEvent.EVENT; import org.weasis.core.ui.editor.SeriesViewerListener; import org.weasis.core.ui.editor.image.ImageViewerPlugin; import org.weasis.core.ui.editor.image.ViewCanvas; import org.weasis.core.ui.editor.image.ViewerPlugin; import org.weasis.core.ui.model.layer.LayerAnnotation; import org.weasis.core.ui.util.RotatedIcon; import org.weasis.dicom.codec.DcmMediaReader; import org.weasis.dicom.codec.DicomMediaIO; import org.weasis.dicom.codec.DicomSpecialElement; import org.weasis.dicom.codec.TagD.Level; import org.weasis.dicom.explorer.wado.DicomManager; public class DicomFieldsView extends JTabbedPane implements SeriesViewerListener { private static final Logger LOGGER = LoggerFactory.getLogger(DicomFieldsView.class); private final JScrollPane allPane = new JScrollPane(); private final JScrollPane limitedPane = new JScrollPane(); private final JTextPane jTextPaneLimited = new JTextPane(); private final JTextPane jTextPaneAll = new JTextPane(); private MediaElement currentMedia; private MediaSeries<?> currentSeries; private boolean anonymize = false; private static final Highlighter.HighlightPainter searchHighlightPainter = new SearchHighlightPainter(new Color(255, 125, 0)); private static final Highlighter.HighlightPainter searchResultHighlightPainter = new SearchHighlightPainter(Color.YELLOW); public DicomFieldsView() { JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); addTab(Messages.getString("DicomFieldsView.limited"), null, panel, null); //$NON-NLS-1$ panel.add(new SearchPanel(jTextPaneLimited), BorderLayout.NORTH); panel.add(limitedPane, BorderLayout.CENTER); jTextPaneLimited.setBorder(new EmptyBorder(5, 5, 5, 5)); // Keep this order to avoid build a default editor HTMLEditorKit kit = JMVUtils.buildHTMLEditorKit(jTextPaneLimited); jTextPaneLimited.setEditorKit(kit); jTextPaneLimited.setContentType("text/html"); //$NON-NLS-1$ jTextPaneLimited.setEditable(false); JMVUtils.addStylesToHTML(jTextPaneLimited.getStyledDocument()); JPanel dump = new JPanel(); dump.setLayout(new BorderLayout()); dump.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); addTab(Messages.getString("DicomFieldsView.all"), null, dump, null); //$NON-NLS-1$ dump.add(new SearchPanel(jTextPaneAll), BorderLayout.NORTH); dump.add(allPane, BorderLayout.CENTER); jTextPaneAll.setBorder(new EmptyBorder(5, 5, 5, 5)); jTextPaneAll.setEditorKit(kit); jTextPaneAll.setContentType("text/html"); //$NON-NLS-1$ jTextPaneAll.setEditable(false); JMVUtils.addStylesToHTML(jTextPaneAll.getStyledDocument()); setPreferredSize(new Dimension(400, 300)); setMinimumSize(new Dimension(150, 50)); this.addChangeListener(changeEvent -> changeDicomInfo(currentSeries, currentMedia)); } @Override public void changingViewContentEvent(SeriesViewerEvent event) { EVENT type = event.getEventType(); if (EVENT.SELECT.equals(type) || EVENT.LAYOUT.equals(type) || EVENT.ANONYM.equals(type)) { currentMedia = event.getMediaElement(); currentSeries = event.getSeries(); if (event.getSeriesViewer() instanceof ImageViewerPlugin) { ViewCanvas<?> sel = ((ImageViewerPlugin<?>) event.getSeriesViewer()).getSelectedImagePane(); if (sel != null) { anonymize = sel.getInfoLayer().getDisplayPreferences(LayerAnnotation.ANONYM_ANNOTATIONS); } } changeDicomInfo(currentSeries, currentMedia); } } private void changeDicomInfo(MediaSeries<?> series, MediaElement media) { int index = getSelectedIndex(); if (index == 0) { jTextPaneLimited.requestFocusInWindow(); displayLimitedDicomInfo(series, media); } else { jTextPaneAll.requestFocusInWindow(); displayAllDicomInfo(series, media); } } private void displayAllDicomInfo(MediaSeries<?> series, MediaElement media) { StyledDocument doc = jTextPaneAll.getStyledDocument(); int oldCaretPosition = jTextPaneAll.getCaretPosition(); try { // clear previous text doc.remove(0, doc.getLength()); if (media != null) { MediaReader loader = media.getMediaReader(); if (loader instanceof DicomMediaIO) { DicomMetaData metaData = null; try { metaData = (DicomMetaData) ((DicomMediaIO) loader).getStreamMetadata(); } catch (IOException e) { LOGGER.error("Get metadata", e); //$NON-NLS-1$ } if (metaData != null) { printAttribute(metaData.getFileMetaInformation(), doc); printAttribute(metaData.getAttributes(), doc); } } else if (loader instanceof DcmMediaReader) { printAttribute(((DcmMediaReader) loader).getDicomObject(), doc); } // Remove first return doc.remove(0, 1); } } catch (BadLocationException e) { LOGGER.error("Clear document", e); //$NON-NLS-1$ } oldCaretPosition = oldCaretPosition > doc.getLength() ? doc.getLength() : oldCaretPosition; jTextPaneAll.setCaretPosition(oldCaretPosition); allPane.setViewportView(jTextPaneAll); } private static void printAttribute(Attributes dcmObj, StyledDocument doc) { if (dcmObj != null) { int[] tags = dcmObj.tags(); for (int tag : tags) { printElement(dcmObj, tag, doc); } } } private static void printElement(Attributes dcmObj, int tag, StyledDocument doc) { StringBuilder buf = new StringBuilder(TagUtils.toString(tag)); buf.append(" ["); //$NON-NLS-1$ VR vr = dcmObj.getVR(tag); buf.append(vr.toString()); buf.append("] "); //$NON-NLS-1$ String privateCreator = dcmObj.privateCreatorOf(tag); String word = ElementDictionary.keywordOf(tag, privateCreator); if (!StringUtil.hasText(word)) { word = "PrivateTag"; //$NON-NLS-1$ } buf.append(word); buf.append(StringUtil.COLON_AND_SPACE); int level = dcmObj.getLevel(); if (level > 0) { buf.insert(0, "-->"); //$NON-NLS-1$ } for (int i = 1; i < level; i++) { buf.insert(0, "--"); //$NON-NLS-1$ } Sequence seq = dcmObj.getSequence(tag); if (seq != null) { if (!seq.isEmpty()) { printSequence(seq, doc, buf); } else { buf.insert(0, "\n"); //$NON-NLS-1$ printItem(doc, buf.toString(), null); } } else { buf.insert(0, "\n"); //$NON-NLS-1$ if (vr.isInlineBinary()) { buf.append("binary data"); //$NON-NLS-1$ printItem(doc, buf.toString(), null); } else { printItem(doc, buf.toString(), null); buf = new StringBuilder(); String[] value = dcmObj.getStrings(privateCreator, tag); if (value != null && value.length > 0) { buf.append(value[0]); for (int i = 1; i < value.length; i++) { buf.append("\\"); //$NON-NLS-1$ buf.append(value[i]); } if (buf.length() > 256) { buf.setLength(253); buf.append("..."); //$NON-NLS-1$ } } printItem(doc, buf.toString(), doc.getStyle("bold")); //$NON-NLS-1$ } } } private static void printItem(StyledDocument doc, String val, AttributeSet attribute) { try { doc.insertString(doc.getLength(), val, attribute); } catch (BadLocationException e) { AuditLog.logError(LOGGER, e, "Error on writing dicom item!"); //$NON-NLS-1$ } } private static void printSequence(Sequence seq, StyledDocument doc, StringBuilder buf) { if (seq != null) { buf.append(seq.size()); if (seq.size() <= 1) { buf.append(" item"); //$NON-NLS-1$ } else { buf.append(" items"); //$NON-NLS-1$ } buf.insert(0, "\n"); //$NON-NLS-1$ printItem(doc, buf.toString(), null); for (int i = 0; i < seq.size(); i++) { Attributes attributes = seq.get(i); int level = attributes.getLevel(); StringBuilder buffer = new StringBuilder(); if (level > 0) { buffer.insert(0, "-->"); //$NON-NLS-1$ } for (int k = 1; k < level; k++) { buffer.insert(0, "--"); //$NON-NLS-1$ } buffer.append(" ITEM #"); //$NON-NLS-1$ buffer.append(i + 1); buffer.insert(0, "\n"); //$NON-NLS-1$ printItem(doc, buffer.toString(), null); int[] tags = attributes.tags(); for (int tag : tags) { printElement(attributes, tag, doc); } } } else { buf.insert(0, "\n"); //$NON-NLS-1$ printItem(doc, buf.toString(), null); } } private void displayLimitedDicomInfo(MediaSeries<?> series, MediaElement media) { StyledDocument doc = jTextPaneLimited.getStyledDocument(); int oldCaretPosition = jTextPaneLimited.getCaretPosition(); try { // clear previous text doc.remove(0, doc.getLength()); } catch (BadLocationException e) { LOGGER.error("Clear document", e); //$NON-NLS-1$ } if (series != null && media != null) { Object tagValue = series.getTagValue(TagW.ExplorerModel); if (tagValue instanceof DicomModel) { DicomModel model = (DicomModel) tagValue; MediaReader loader = media.getMediaReader(); if (loader instanceof DcmMediaReader) { List<DicomData> list = DicomManager.getInstance().getLimitedDicomTags(); for (DicomData dicomData : list) { writeItems(dicomData, getGroup(model, series, dicomData), doc); } } } } oldCaretPosition = oldCaretPosition > doc.getLength() ? doc.getLength() : oldCaretPosition; jTextPaneLimited.setCaretPosition(oldCaretPosition); limitedPane.setViewportView(jTextPaneLimited); } private MediaSeriesGroup getGroup(DicomModel model, MediaSeries<?> series, DicomData dicomData) { Level level = dicomData.getLevel(); if (Level.PATIENT.equals(level)) { return model.getParent(series, DicomModel.patient); } else if (Level.STUDY.equals(level)) { return model.getParent(series, DicomModel.study); } else if (Level.SERIES.equals(level)) { return model.getParent(series, DicomModel.series); } return null; } private void writeItems(DicomData dicomData, TagReadable group, StyledDocument doc) { int insertTitle = doc.getLength(); boolean exist = false; for (TagView t : dicomData.getInfos()) { for (TagW tag : t.getTag()) { if (!anonymize || tag.getAnonymizationType() != 1) { try { Object val = TagUtil.getTagValue(tag, group, currentMedia); if (val != null) { exist = true; doc.insertString(doc.getLength(), tag.getDisplayedName(), null); doc.insertString(doc.getLength(), StringUtil.COLON_AND_SPACE + tag.getFormattedTagValue(val, null) + "\n", //$NON-NLS-1$ doc.getStyle("bold")); //$NON-NLS-1$ break; } } catch (BadLocationException e) { LOGGER.error("Writing textissue", e); //$NON-NLS-1$ } } } } if (exist) { try { String formatTitle = insertTitle < 3 ? dicomData.getTitle() + "\n" : "\n" + dicomData.getTitle() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ doc.insertString(insertTitle, formatTitle, doc.getStyle("title")); //$NON-NLS-1$ } catch (BadLocationException e) { LOGGER.error("Writing text issue", e); //$NON-NLS-1$ } } } public static void displayHeader(ImageViewerPlugin<?> container) { if (container != null) { ViewCanvas<?> selView = container.getSelectedImagePane(); if (selView != null) { ImageElement img = selView.getImage(); if (img != null) { JFrame frame = new JFrame(Messages.getString("DicomExplorer.dcmInfo")); //$NON-NLS-1$ frame.setSize(500, 630); DicomFieldsView view = new DicomFieldsView(); view.changingViewContentEvent( new SeriesViewerEvent(container, selView.getSeries(), img, EVENT.SELECT)); JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.add(view); frame.getContentPane().add(panel); frame.setAlwaysOnTop(true); JMVUtils.showCenterScreen(frame, container); } } } } public static void displayHeaderForSpecialElement(ViewerPlugin<?> container, Series<?> series) { if (container != null && series != null) { DicomSpecialElement dcm = DicomModel.getFirstSpecialElement(series, DicomSpecialElement.class); if (dcm != null) { JFrame frame = new JFrame(Messages.getString("DicomExplorer.dcmInfo")); //$NON-NLS-1$ frame.setSize(500, 630); DicomFieldsView view = new DicomFieldsView(); view.changingViewContentEvent(new SeriesViewerEvent(container, series, dcm, EVENT.SELECT)); JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.add(view); frame.getContentPane().add(panel); frame.setAlwaysOnTop(true); JMVUtils.showCenterScreen(frame, container); } } } static class SearchHighlightPainter extends DefaultHighlighter.DefaultHighlightPainter { public SearchHighlightPainter(Color color) { super(color); } } static class SearchPanel extends JPanel { private final List<Integer> searchPostions = new ArrayList<>(); private final JTextComponent textComponent; private int currentSearchIndex = 0; private String currentSearchPattern; public SearchPanel(JTextComponent textComponent) { super(); this.textComponent = textComponent; this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); init(); } private void init() { this.add(new JLabel(Messages.getString("DicomFieldsView.search") + StringUtil.COLON_AND_SPACE)); //$NON-NLS-1$ final JTextField tf = new JTextField(); JMVUtils.setPreferredWidth(tf, 300, 100); tf.addActionListener(evt -> { currentSearchPattern = tf.getText().trim(); highlight(currentSearchPattern); if (!searchPostions.isEmpty()) { try { textComponent.scrollRectToVisible(textComponent.modelToView(searchPostions.get(0))); textComponent.requestFocusInWindow(); } catch (BadLocationException e) { LOGGER.error("Scroll to highight", e); //$NON-NLS-1$ } } }); this.add(tf); JButton up = new JButton(new ImageIcon(SeriesViewerListener.class.getResource("/icon/up.png"))); //$NON-NLS-1$ up.setToolTipText(Messages.getString("DicomFieldsView.previous")); //$NON-NLS-1$ up.addActionListener(evt -> previous()); this.add(up); JButton down = new JButton(new RotatedIcon(new ImageIcon(SeriesViewerListener.class.getResource("/icon/up.png")), //$NON-NLS-1$ RotatedIcon.Rotate.UPSIDE_DOWN)); down.setToolTipText(Messages.getString("DicomFieldsView.next")); //$NON-NLS-1$ down.addActionListener(evt -> next()); this.add(down); textComponent.addKeyListener(new KeyListener() { @Override public void keyTyped(KeyEvent e) { } @Override public void keyReleased(KeyEvent e) { if (e.isShiftDown() && e.getKeyCode() == KeyEvent.VK_F3) { previous(); } else if (e.getKeyCode() == KeyEvent.VK_F3) { next(); } } @Override public void keyPressed(KeyEvent e) { } }); textComponent.setFocusable(true); } private void previous() { if (!searchPostions.isEmpty()) { currentSearchIndex = currentSearchIndex <= 0 ? searchPostions.size() - 1 : currentSearchIndex - 1; showCurrentSearch(currentSearchPattern); } } private void next() { if (!searchPostions.isEmpty()) { currentSearchIndex = currentSearchIndex >= searchPostions.size() - 1 ? 0 : currentSearchIndex + 1; showCurrentSearch(currentSearchPattern); } } public void highlight(String pattern) { removeHighlights(textComponent); searchPostions.clear(); if (StringUtil.hasText(pattern)) { try { Highlighter hilite = textComponent.getHighlighter(); Document doc = textComponent.getDocument(); String text = doc.getText(0, doc.getLength()).toUpperCase(); String patternUp = pattern.toUpperCase(); int pos = 0; while ((pos = text.indexOf(patternUp, pos)) >= 0) { if (searchPostions.isEmpty()) { hilite.addHighlight(pos, pos + patternUp.length(), searchHighlightPainter); } else { hilite.addHighlight(pos, pos + patternUp.length(), searchResultHighlightPainter); } searchPostions.add(pos); pos += patternUp.length(); } } catch (BadLocationException e) { LOGGER.error("Highight result of search", e); //$NON-NLS-1$ } } } public void removeHighlights(JTextComponent textComonent) { Highlighter hilite = textComonent.getHighlighter(); for (Highlighter.Highlight highlight : hilite.getHighlights()) { if (highlight.getPainter() instanceof SearchHighlightPainter) { hilite.removeHighlight(highlight); } } } public void showCurrentSearch(String pattern) { if (!searchPostions.isEmpty() && StringUtil.hasText(pattern)) { removeHighlights(textComponent); try { if (currentSearchIndex < 0 || currentSearchIndex >= searchPostions.size()) { currentSearchIndex = 0; } int curPos = searchPostions.get(currentSearchIndex); Highlighter hilite = textComponent.getHighlighter(); for (Integer pos : searchPostions) { if (pos == curPos) { hilite.addHighlight(pos, pos + pattern.length(), searchHighlightPainter); } else { hilite.addHighlight(pos, pos + pattern.length(), searchResultHighlightPainter); } } textComponent.scrollRectToVisible(textComponent.modelToView(curPos)); } catch (BadLocationException e) { LOGGER.error("Highight result of search", e); //$NON-NLS-1$ } } } } public static class DicomData { private final String title; private final TagView[] infos; private final Level level; public DicomData(String title, TagView[] infos, Level level) { if (infos == null) { throw new IllegalArgumentException(); } this.title = title; this.infos = infos; this.level = level; for (TagView tagView : infos) { for (TagW tag : tagView.getTag()) { DicomMediaIO.tagManager.addTag(tag, level); } } } public TagView[] getInfos() { return infos; } public String getTitle() { return title; } public Level getLevel() { return level; } } }