imageviewer.ui.dialog.ps.PSDialogPanel.java Source code

Java tutorial

Introduction

Here is the source code for imageviewer.ui.dialog.ps.PSDialogPanel.java

Source

/*******************************************************************************
 * Copyright (c) 2011 UCLA Medical Imaging Informatics Group
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 ******************************************************************************/
package imageviewer.ui.dialog.ps;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;

import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.EventObject;
import java.util.List;

import javax.swing.AbstractCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import javax.swing.border.EmptyBorder;
import javax.swing.event.CaretListener;
import javax.swing.event.CaretEvent;

import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.painter.Painter;

import imageserver.model.Associable;
import imageserver.model.AssociatedData;
import imageserver.model.AssociablePresentationState;
import imageserver.model.AssociatedData.AssocDataType;
import imageserver.model.Series;

import imageviewer.model.Image;
import imageviewer.model.PresentationState;

import imageviewer.system.ImageViewerClientNode;
import imageviewer.system.SaveStack;

import imageviewer.ui.ApplicationContext;
import imageviewer.ui.ApplicationPanel;
import imageviewer.ui.FloatingPanel;
import imageviewer.ui.FloatingPanelActionListener;
import imageviewer.ui.UserManager;

import imageviewer.ui.dialog.DialogUtil;
import imageviewer.ui.image.ImagePanel;
import imageviewer.ui.swing.MenuAction;
import imageviewer.ui.swing.TableHeaderRenderer;
import imageviewer.ui.swing.VerticalLabelUI;

// =======================================================================

public class PSDialogPanel extends JXPanel
        implements FloatingPanelActionListener, ActionListener, ItemListener, CaretListener {

    private static final JCheckBox ASK_CB_AUTHOR = new JCheckBox("Do not show this message again.", false);
    private static final JCheckBox ASK_CB_DELETE = new JCheckBox("Do not show this message again.", false);

    private static final SimpleDateFormat DATE_FORMAT_SRC = new SimpleDateFormat("yyyyMMdd HHmmss");
    private static final SimpleDateFormat DATE_FORMAT_TRG = new SimpleDateFormat("MM/dd/yy h:mm a");

    private static final int[] COLUMN_WIDTH = new int[] { 100, 200 };
    private static PSDialogPanel PSDP_INSTANCE = null;

    public static PSDialogPanel getInstance() {
        if (PSDP_INSTANCE == null)
            PSDP_INSTANCE = new PSDialogPanel();
        return PSDP_INSTANCE;
    }

    // =======================================================================

    PSCollectionTableModel pctm = new PSCollectionTableModel();
    JXTable collectionTable = null, fixedColumnTable = null;
    PSViewPanel psView = new PSViewPanel();
    String currentAssocKey = null;
    PSThumbnailPanel currentSelection = null;
    JScrollPane studyScrollPane = null;
    JButton deleteListButton = new JButton("Delete current collection");
    JButton deletePSButton = new JButton("Delete current presentation state");
    JTextField authorField = new JTextField(25);
    JTextField timestampField = new JTextField(30);
    JTextField descriptionField = new JTextField(256);

    private PSDialogPanel() {

        super();
        FormLayout fl = new FormLayout(
                "fill:pref:grow,2dlu,fill:pref:grow,4dlu,pref,2dlu,fill:pref:grow,2dlu,pref,2dlu,pref,2dlu,right:pref:grow",
                "pref,4px,pref,2px,pref,10px,fill:pref:grow,2px,pref,2px,pref,2px,top:pref");
        setLayout(fl);
        setBorder(new EmptyBorder(5, 5, 5, 5));
        setPreferredSize(new Dimension(995, 675));
        setLocation(50, 50);

        JTextArea psPanelDescription = DialogUtil.createTextArea(
                "Click on the thumbnails to view the presentation state in full.  Drag images (ctrl-left-click drag) "
                        + "to rearrange thumbnails within a row.");
        JTextArea psViewDescription = DialogUtil.createTextArea(
                "Drag images (ctrl-left-click drag) into the panel below to capture presentation states.  "
                        + "You can enter additional descriptive information into associated the text fields.");

        JButton closeButton = new JButton("Close");
        closeButton.setActionCommand("close");
        closeButton.addActionListener(this);
        JLabel studyLabel = new JLabel("Collection navigation", JLabel.LEFT);
        JLabel viewLabel = new JLabel("Presentation state viewer", JLabel.LEFT);
        JSeparator separator1 = new JSeparator();
        separator1.setPreferredSize(new Dimension(415, 5));
        JSeparator separator2 = new JSeparator();
        separator2.setPreferredSize(new Dimension(525, 5));

        PSTableCellRenderer pstcr = new PSTableCellRenderer();
        PSTableCellEditor pstce = new PSTableCellEditor();
        TableHeaderRenderer thr = new TableHeaderRenderer(Color.black);

        collectionTable = new JXTable(pctm) {
            public void toggleSortOrder(int columnIndex) {
                pctm.toggleSort();
            }
        };
        collectionTable.setHorizontalScrollEnabled(true);
        collectionTable.setShowHorizontalLines(true);
        collectionTable.setShowVerticalLines(false);
        collectionTable.setColumnMargin(1);
        collectionTable.setColumnControlVisible(false);
        collectionTable.setRolloverEnabled(false);
        collectionTable.setRowSelectionAllowed(true);
        collectionTable.setColumnSelectionAllowed(false);
        collectionTable.setRowHeight(132);
        collectionTable.getTableHeader().setReorderingAllowed(false);
        collectionTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        for (int i = 0; i < COLUMN_WIDTH.length; i++) {
            TableColumn column = collectionTable.getColumnModel().getColumn(i);
            column.setPreferredWidth(COLUMN_WIDTH[i]);
            column.setHeaderRenderer(thr);
            column.setCellRenderer(pstcr);
            column.setCellEditor(pstce);
        }

        // Mutilate the collectionTable and its model so that we can put
        // just the thumbnails into a scrollpane and freeze the first
        // column.  Support for fixed columns not forthcoming anytime soon
        // from swingx or sun...Override the togglesortorder so we know
        // when a sort even occurs, and then we can also sort the
        // collectionTable, too.

        fixedColumnTable = new JXTable(collectionTable.getModel()) {
            public void toggleSortOrder(int columnIndex) {
                pctm.toggleSort();
            }
        };
        fixedColumnTable.setFocusable(false);
        fixedColumnTable.setSelectionModel(collectionTable.getSelectionModel());
        fixedColumnTable.getTableHeader().setReorderingAllowed(false);
        fixedColumnTable.getTableHeader().setResizingAllowed(false);
        fixedColumnTable.getColumnModel().getColumn(0).setPreferredWidth(45);
        fixedColumnTable.getColumnModel().getColumn(0).setHeaderRenderer(thr);
        fixedColumnTable.getColumnModel().getColumn(0).setCellRenderer(pstcr);
        fixedColumnTable.setHorizontalScrollEnabled(true);
        fixedColumnTable.setShowHorizontalLines(true);
        fixedColumnTable.setShowVerticalLines(true);
        fixedColumnTable.setColumnMargin(1);
        fixedColumnTable.setColumnControlVisible(false);
        fixedColumnTable.setRolloverEnabled(false);
        fixedColumnTable.setRowSelectionAllowed(true);
        fixedColumnTable.setColumnSelectionAllowed(false);
        fixedColumnTable.setRowHeight(132);
        fixedColumnTable.setAutoscrolls(false);
        fixedColumnTable.getTableHeader().setReorderingAllowed(false);
        fixedColumnTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        fixedColumnTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
        collectionTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
        collectionTable.setDragEnabled(true);

        // Remove the first column from the main table

        TableColumnModel columnModel = collectionTable.getColumnModel();
        columnModel.removeColumn(columnModel.getColumn(0));

        // Remove the non-fixed columns from the fixed table

        while (fixedColumnTable.getColumnCount() > 1) {
            columnModel = fixedColumnTable.getColumnModel();
            columnModel.removeColumn(columnModel.getColumn(1));
        }

        // Add the fixed table to the scroll pane

        fixedColumnTable.setPreferredScrollableViewportSize(fixedColumnTable.getPreferredSize());

        studyScrollPane = new JScrollPane(collectionTable, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        studyScrollPane.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
        studyScrollPane.getViewport().setBackground(Color.black);
        studyScrollPane.setRowHeaderView(fixedColumnTable);
        studyScrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER, fixedColumnTable.getTableHeader());
        studyScrollPane.setPreferredSize(new Dimension(425, 525));

        // Use a containing JPanel so that the contents are centered in
        // the scrollPane. Grr...

        JPanel viewPanel = new JPanel(new GridBagLayout());
        viewPanel.add(psView, new GridBagConstraints());

        JScrollPane psScrollPane = new JScrollPane(viewPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        psScrollPane.getViewport().setBackground(Color.black);
        psScrollPane.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
        psScrollPane.setPreferredSize(new Dimension(525, 525));

        JComboBox groupByComboBox = new JComboBox(new String[] { "Series", "Study" });
        deleteListButton.setActionCommand("deleteList");
        deleteListButton.addActionListener(this);
        deleteListButton.setEnabled(false);
        deletePSButton.setActionCommand("deletePS");
        deletePSButton.addActionListener(this);
        deletePSButton.setEnabled(false);
        JLabel groupByLabel = new JLabel("Group collections by", JLabel.TRAILING);
        JLabel authorLabel = new JLabel("Author", JLabel.TRAILING);
        JLabel timestampLabel = new JLabel("Created on", JLabel.TRAILING);
        JLabel descriptionLabel = new JLabel("Description", JLabel.TRAILING);
        authorField.setEditable(false);
        timestampField.setEditable(false);

        CellConstraints cc = new CellConstraints();
        add(studyLabel, cc.xy(1, 1));
        add(viewLabel, cc.xywh(5, 1, 9, 1));
        add(separator1, cc.xywh(1, 3, 3, 1));
        add(psPanelDescription, cc.xywh(1, 5, 3, 1));
        add(separator2, cc.xywh(5, 3, 9, 1));
        add(psViewDescription, cc.xywh(5, 5, 9, 1));
        add(studyScrollPane, cc.xywh(1, 7, 3, 1));
        add(groupByLabel, cc.xy(1, 9));
        add(groupByComboBox, cc.xy(3, 9));
        add(deleteListButton, cc.xy(1, 11));
        add(deletePSButton, cc.xy(3, 11));
        add(psScrollPane, cc.xywh(5, 7, 9, 1));
        add(authorLabel, cc.xy(5, 9));
        add(authorField, cc.xy(7, 9));
        add(timestampLabel, cc.xy(9, 9));
        add(timestampField, cc.xywh(11, 9, 3, 1));
        add(descriptionLabel, cc.xy(5, 11));
        add(descriptionField, cc.xywh(7, 11, 7, 1));
        add(closeButton, cc.xy(13, 13));

        setBackgroundPainter((Painter) UIManager.get("FloatingPanel.backgroundPainter"));
        setDropTarget(psView.getDropTarget());

        descriptionField.addCaretListener(this);
        groupByComboBox.addItemListener(this);
    }

    // =======================================================================
    // Attempt to update the current set of information from the (local)
    // archive's information on presentation states.  This action will
    // occur whenever we "switch" to a different patient/object.

    public void update(String assocKey) {

        if ((assocKey != null) && (currentAssocKey != null) && (assocKey.compareTo(currentAssocKey) == 0))
            return;
        psView.setPresentationState(null);
        psView.setSource(null);
        currentAssocKey = assocKey;
        List<Associable> ls = ImageViewerClientNode.getInstance().localFindAssociatedData(assocKey,
                AssocDataType.PRESENTATION_STATE.toString());

        // The list that is sent to us contains the presentation states;
        // what we need are the series that they are associated with.
        // From these series, we should then get the list of presentation
        // states; this will provide the ordering information that we
        // need.

        ArrayList<Series> psSeriesList = new ArrayList<Series>();
        try {
            for (Associable a : ls) {
                if (a instanceof AssociablePresentationState) {
                    PresentationState ps = ((AssociablePresentationState) a).getPresentationState();
                    List<Series> series = ImageViewerClientNode.getInstance()
                            .localFindSeriesByID(ps.getReferencedSeriesKey());
                    if ((series != null) && (!series.isEmpty())) {
                        Series s = series.get(0);
                        if (!psSeriesList.contains(s))
                            psSeriesList.add(s);
                    }
                }
            }
        } catch (Exception exc) {
            exc.printStackTrace();
        }
        clearFields();
        pctm.update(psSeriesList);
    }

    // =======================================================================

    public void setViewPanelAuthor(String author) {
        authorField.setText(author);
    }

    public void setViewPanelTimestamp(String timestamp) {
        timestampField.setText(timestamp);
    }

    public void setViewPanelTimestamp(String date, String time) {
        timestampField.setText(formatDate(date, time));
    }

    public void setViewPanelDescription(String description) {
        descriptionField.setText(description);
    }

    public void setViewPanelEditable(boolean editable) {
        descriptionField.setEditable(editable);
    }

    private void clearFields() {
        authorField.setText("");
        timestampField.setText("");
        descriptionField.setText("");
    }

    // =======================================================================
    /**
     * Create the presentation for the given imagePanel.  The
     * presentation state (PS) asks the underlying image to generate the
     * information, and then creates the thumbnail that is displayed in
     * this dialog panel.  When a new PS is created, it goes on the
     * saveStack, and the dialog is updated to show this new PS object.
     * If no current user is known, we send up a warning dialog to state
     * that a guest author will be specified.
     *
     * @param ip a value of type 'ImagePanel'
     */

    public void createNewPresentationState(ImagePanel ip) {

        String currentUser = UserManager.getCurrentUserName();
        if (currentUser == UserManager.NO_LOGIN) {
            currentUser = "";
            Boolean warnNoLogin = (Boolean) ApplicationContext.getContext()
                    .getProperty(ApplicationContext.WARN_NO_LOGIN);
            if ((warnNoLogin == null) || (warnNoLogin.booleanValue())) {
                int response = ApplicationPanel.getInstance().showDialog(
                        "No login to imageviewer has been supplied. As a result the author field for this presentation state "
                                + "will not be filled in.",
                        new JComponent[] { ASK_CB_AUTHOR }, JOptionPane.INFORMATION_MESSAGE,
                        JOptionPane.DEFAULT_OPTION);
                if (ASK_CB_AUTHOR.isSelected())
                    ApplicationContext.getContext().setProperty(ApplicationContext.WARN_NO_LOGIN,
                            new Boolean(false));
            }
        }

        Image i = ip.getSource();
        PresentationState ps = i.generatePresentationState(ip, currentUser, "", "");
        authorField.setText(ps.getPresentationCreatorName());
        timestampField.setText(formatDate(ps.getPresentationCreationDate(), ps.getPresentationCreationTime()));
        descriptionField.setText("");
        setViewPanelEditable(true);
        PSThumbnailPanel pstp = pctm.addPresentationState(ps, i);
        pstp.setSaved(false);
        SaveStack.getInstance().addSaveable((PSCollection) pstp.getParent());
        if (currentSelection != null)
            currentSelection.setThumbnailSelected(false);
        currentSelection = pstp;
        pstp.setThumbnailSelected(true);
        psView.setSource(i);
        psView.setPresentationState(ps);
    }

    // =======================================================================

    public void closeAction() {
        MenuAction ma = MenuAction.ACTIONS.get(ApplicationContext.DISPLAY_PRESENTATION_STATES_COMMAND);
        ma.getMenuItem().doClick();
    }

    // =======================================================================

    public JXTable getCollectionTable() {
        return collectionTable;
    }

    public JScrollPane getThumbnailScrollPane() {
        return studyScrollPane;
    }

    public PSThumbnailPanel getCurrentSelection() {
        return currentSelection;
    }

    public PSViewPanel getViewPanel() {
        return psView;
    }

    public void setCurrentSelection(PSThumbnailPanel x) {
        currentSelection = x;
        deletePSButton.setEnabled((x == null) ? false : true);
        deleteListButton.setEnabled((x == null) ? false : true);
    }

    // =======================================================================

    private String formatDate(String date, String time) {
        try {
            return DATE_FORMAT_TRG.format(DATE_FORMAT_SRC.parse(date + " " + time));
        } catch (Exception exc) {
            return null;
        }
    }

    // =======================================================================

    public PSThumbnailPanel findThumbnail(Point p) {

        // Play some trickery to try and figure out which of the thumbnail
        // panels we are hitting within the table.  Basically, the x
        // coordinate of the mouse event will be correct, but the y will
        // be screwed up.  And because of all the issues with the JTable
        // and mouse events, we need to forward to the right one by
        // computing it explicitly.  Use the X value divided by the width
        // of thumbnail to get to the right component.

        int row = p.y / collectionTable.getRowHeight();
        if (row >= collectionTable.getRowCount() || row < 0)
            return null;
        Object value = collectionTable.getValueAt(row, 0);
        if (value instanceof PSCollection) {
            PSCollection psc = (PSCollection) value;
            if (p.x < (128 * psc.getComponentCount())) {
                int thumbnailIndex = (int) Math.floor(p.x / 128);
                return (PSThumbnailPanel) psc.getComponent(thumbnailIndex);
            }
        }
        return null;
    }

    // =======================================================================

    public void actionPerformed(ActionEvent e) {

        if ("close".equals(e.getActionCommand())) {
            FloatingPanel fp = (FloatingPanel) SwingUtilities.getAncestorOfClass(FloatingPanel.class, this);
            if (fp != null)
                fp.actionPerformed(new ActionEvent(this, 1, "close"));
        } else if ("deletePS".equals(e.getActionCommand())) {
            if (currentSelection == null)
                return;
            Boolean warnDelete = (Boolean) ApplicationContext.getContext()
                    .getProperty(ApplicationContext.WARN_PS_DELETE);
            if ((warnDelete == null) || (warnDelete.booleanValue())) {
                int response = ApplicationPanel.getInstance().showDialog(
                        "Are you sure you want to delete the currently selected presentation state? It will be removed this collection.",
                        new JComponent[] { ASK_CB_DELETE }, JOptionPane.QUESTION_MESSAGE,
                        JOptionPane.OK_CANCEL_OPTION);
                if (ASK_CB_DELETE.isSelected())
                    ApplicationContext.getContext().setProperty(ApplicationContext.WARN_PS_DELETE,
                            new Boolean(false));
                if (response == JOptionPane.CANCEL_OPTION)
                    return;
            }
            currentSelection.delete();
            psView.setSource(null);
            psView.setPresentationState(null);
            clearFields();
            setViewPanelEditable(false);
            setCurrentSelection(null);
        } else if ("deleteList".equals(e.getActionCommand())) {

        }
    }

    // =======================================================================

    public void itemStateChanged(ItemEvent e) {

        if (e.getStateChange() == ItemEvent.SELECTED) {
            String groupBy = (String) e.getItem();
            pctm.updateGrouping(("Study".equals(groupBy)) ? PSCollectionTableModel.GroupByType.STUDY
                    : PSCollectionTableModel.GroupByType.SERIES);
        }
    }

    // =======================================================================

    public void caretUpdate(CaretEvent ce) {

        if ((currentSelection != null) && (descriptionField.isEditable())) {
            PresentationState ps = currentSelection.getPresentationState();
            ps.setPresentationDescription(descriptionField.getText());
        }
    }

    // =======================================================================

    private class PSTableCellRenderer extends DefaultTableCellRenderer {

        FontMetrics fm = getFontMetrics(getFont());
        String ellipses = new String("...");

        private String createHTMLString(String[] description) {

            String htmlString = new String("<html><p align=\"right\">");
            for (int loop = 0, n = description.length; loop < n; loop++) {
                String s = description[loop];
                if (fm.stringWidth(s) > 120) {
                    int textWidth = fm.stringWidth(ellipses);
                    int i = 0;
                    for (int nChars = s.length() - 1; i < nChars; i++) {
                        textWidth += fm.charWidth(s.charAt(i));
                        if (textWidth > 120)
                            break;
                    }
                    s = s.substring(0, i + 1) + ellipses;
                }
                htmlString += (loop == 0) ? s : ("<br>" + s);
            }
            return htmlString += "</p></html>";
        }

        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                boolean hasFocus, int row, int column) {

            if (value instanceof PSCollection)
                return (Component) value;
            JXPanel jxp = new JXPanel(new FlowLayout(FlowLayout.LEFT, 2, 5));
            Painter p = (Painter) (UIManager
                    .get((isSelected) ? "Table.selectedRowHeaderPainter" : "Table.rowHeaderPainter"));
            jxp.setBackgroundPainter(p);
            JLabel jl = new JLabel(createHTMLString((String[]) value), JLabel.RIGHT);
            jl.setUI(new VerticalLabelUI(false));
            jl.setBorder(new EmptyBorder(0, 0, 0, 6));
            jxp.add(jl);
            return jxp;
        }
    }

    // =======================================================================
    // Class used to enable mouse events to pass through from the table
    // to the child component (otherwise JTable will absorb it all).
    // The isCellEditable() method is basically a hack so that we can
    // use single-clicks to select a thumbnail.  We have to explicitly
    // send the event for some reason...

    private class PSTableCellEditor extends AbstractCellEditor implements TableCellEditor {

        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int rowIndex,
                int vColIndex) {
            return (value instanceof PSCollection) ? (Component) value : null;
        }

        public Object getCellEditorValue() {
            return null;
        }

        public boolean isCellEditable(EventObject evt) {

            if (evt instanceof MouseEvent) {
                MouseEvent me = (MouseEvent) evt;
                if (me.getClickCount() > 0) {
                    PSThumbnailPanel pstp = findThumbnail(me.getPoint());
                    if (pstp != null)
                        pstp.mouseClicked(me);
                }
            }
            return true;
        }
    }
}