/*
* ------------------------------------------------------------------------
*
* Copyright (C) 2003 - 2013
* University of Konstanz, Germany and
* KNIME GmbH, Konstanz, Germany
* Website: http://www.knime.org; Email: contact@knime.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, Version 3, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>.
*
* Additional permission under GNU GPL version 3 section 7:
*
* KNIME interoperates with ECLIPSE solely via ECLIPSE's plug-in APIs.
* Hence, KNIME and ECLIPSE are both independent programs and are not
* derived from each other. Should, however, the interpretation of the
* GNU GPL Version 3 ("License") under any applicable laws result in
* KNIME and ECLIPSE being a combined program, KNIME GMBH herewith grants
* you the additional permission to use and propagate KNIME together with
* ECLIPSE with only the license terms in place for ECLIPSE applying to
* ECLIPSE and the GNU GPL Version 3 applying for KNIME, provided the
* license terms of ECLIPSE themselves allow for the respective use and
* propagation of ECLIPSE together with KNIME.
*
* Additional permission relating to nodes for KNIME that extend the Node
* Extension (and in particular that are based on subclasses of NodeModel,
* NodeDialog, and NodeView) and that only interoperate with KNIME through
* standard APIs ("Nodes"):
* Nodes are deemed to be separate and independent programs and to not be
* covered works. Notwithstanding anything to the contrary in the
* License, the License does not apply to Nodes, you are not required to
* license Nodes under the License, and you are granted a license to
* prepare and propagate Nodes, in each case even if such Nodes are
* propagated with or for interoperation with KNIME. The owner of a Node
* may freely choose the license terms applicable to such Node, including
* when such Node is propagated with or for interoperation with KNIME.
* --------------------------------------------------------------------- *
*
*/
package org.knime.knip.base.nodes.view;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingWorker;
import javax.swing.border.BevelBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.knime.core.data.DataCell;
import org.knime.core.data.DataValue;
import org.knime.core.node.BufferedDataTableHolder;
import org.knime.core.node.NodeLogger;
import org.knime.core.node.NodeModel;
import org.knime.core.node.NodeView;
import org.knime.core.node.tableview.TableContentModel;
import org.knime.core.node.tableview.TableContentView;
import org.knime.core.node.tableview.TableView;
import org.knime.knip.cellviewer.CellViewsManager;
import org.knime.knip.cellviewer.interfaces.CellViewFactory;
import org.knime.knip.core.ui.event.EventListener;
import org.knime.knip.core.ui.event.EventService;
import org.knime.knip.core.ui.event.EventServiceClient;
import org.knime.knip.core.ui.imgviewer.events.PlaneSelectionEvent;
import org.knime.knip.core.ui.imgviewer.events.TableOverviewDisableEvent;
import org.knime.knip.core.ui.imgviewer.events.TablePositionEvent;
import org.knime.knip.core.ui.imgviewer.panels.ViewerControlEvent;
import org.knime.knip.core.ui.imgviewer.panels.ViewerScrollEvent;
import org.knime.knip.core.ui.imgviewer.panels.ViewerScrollEvent.Direction;
import org.knime.knip.core.util.waitingindicator.WaitingIndicatorUtils;
import org.knime.node.v210.ViewDocument.View;
import org.knime.node.v210.ViewsDocument.Views;
/*
* ------------------------------------------------------------------------
*
* Copyright (C) 2003, 2010
* University of Konstanz, Germany and
* KNIME GmbH, Konstanz, Germany
* Website: http://www.knime.org; Email: contact@knime.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, Version 3, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>.
*
* Additional permission under GNU GPL version 3 section 7:
*
* KNIME interoperates with ECLIPSE solely via ECLIPSE's plug-in APIs.
* Hence, KNIME and ECLIPSE are both independent programs and are not
* derived from each other. Should, however, the interpretation of the
* GNU GPL Version 3 ("License") under any applicable laws result in
* KNIME and ECLIPSE being a combined program, KNIME GMBH herewith grants
* you the additional permission to use and propagate KNIME together with
* ECLIPSE with only the license terms in place for ECLIPSE applying to
* ECLIPSE and the GNU GPL Version 3 applying for KNIME, provided the
* license terms of ECLIPSE themselves allow for the respective use and
* propagation of ECLIPSE together with KNIME.
*
* Additional permission relating to nodes for KNIME that extend the Node
* Extension (and in particular that are based on subclasses of NodeModel,
* NodeDialog, and NodeView) and that only interoperate with KNIME through
* standard APIs ("Nodes"):
* Nodes are deemed to be separate and independent programs and to not be
* covered works. Notwithstanding anything to the contrary in the
* License, the License does not apply to Nodes, you are not required to
* license Nodes under the License, and you are granted a license to
* prepare and propagate Nodes, in each case even if such Nodes are
* propagated with or for interoperation with KNIME. The owner of a Node
* may freely choose the license terms applicable to such Node, including
* when such Node is propagated with or for interoperation with KNIME.
* ------------------------------------------------------------------------
*
* History
* 29 Jan 2010 (hornm): created
*/
/**
*
* @author <a href="mailto:dietzc85@googlemail.com">Christian Dietz</a>
* @author <a href="mailto:horn_martin@gmx.de">Martin Horn</a>
* @author <a href="mailto:michael.zinsmaier@googlemail.com">Michael Zinsmaier</a>
* @author <a href="mailto:jonathan.hale@uni.kn">Jonathan Hale</a>
*
* @param <T> {@link NodeModel} subclass this {@link TableCellViewNodeView} belongs to.
*/
public class TableCellViewNodeView<T extends NodeModel & BufferedDataTableHolder> extends NodeView<T>
implements EventServiceClient {
private static final NodeLogger LOGGER = NodeLogger.getLogger(TableCellViewNodeView.class);
/**
* Add the description of the view.
*
* @param views
*/
public static void addViewDescriptionTo(final Views views) {
final View view = views.addNewView();
view.setIndex(new BigInteger("0"));
view.setName("Image Viewer");
final Map<CellViewFactory, List<String>> descs =
CellViewsManager.getInstance().getTableCellViewDescriptions();
view.newCursor()
.setTextValue("Another, possibly interactive, view on table cells. Displays the selected cells with their associated viewer if it exists. Available views are:");
view.addNewBr();
for (final Entry<CellViewFactory, List<String>> entry : descs.entrySet()) {
view.addB("- " + entry.getKey().getCellViewName());
view.addNewBr();
for (final String d : entry.getValue()) {
view.addI("-- " + d);
view.addNewBr();
}
}
}
/**
* Add the description of the view.
*
* @param views
*
* @deprecated Consider using {@link org.knime.node.v210.ViewsDocument.Views}
*/
@Deprecated
public static void addViewDescriptionTo(final org.knime.node2012.ViewsDocument.Views views) {
final org.knime.node2012.ViewDocument.View view = views.addNewView();
view.setIndex(0);
view.setName("Image Viewer");
final Map<Class<? extends DataValue>, List<String>> descs =
TableCellViewsManager.getInstance().getTableCellViewDescriptions();
view.newCursor()
.setTextValue("Another, possibly interactive, view on table cells. Displays the selected cells with their associated viewer if it exists. Available views are:");
view.addNewBr();
for (final Entry<Class<? extends DataValue>, List<String>> entry : descs.entrySet()) {
view.addB("- " + entry.getKey().getSimpleName());
view.addNewBr();
for (final String d : entry.getValue()) {
view.addI("-- " + d);
view.addNewBr();
}
}
}
protected Map<String, TableCellView> m_cellViewCache;
protected Map<TableCellViewFactory, List<TableCellView>> m_factoryCache;
// protected JTabbedPane m_cellViewTabs;
protected ChangeListener m_changeListener;
protected ActionListener m_bottomQuickViewListener;
protected ActionListener m_leftQuickViewListener;
protected ActionListener m_OverviewListener;
protected int m_col = -1;
protected List<DataCell> m_currentCells = new LinkedList<DataCell>();
protected List<TableCellView> m_currentCachedViews;
protected ListSelectionListener m_listSelectionListenerA;
protected ListSelectionListener m_listSelectionListenerB;
protected final int m_portIdx;
protected boolean m_adjusting;
protected JPanel m_tableViewPanel;
protected static final String m_defStatusBarText =
"Click on a cell or drag and select multiple cells to continue ...";
// private static final ExecutorService UPDATE_EXECUTOR = Executors
// .newCachedThreadPool(new ThreadFactory() {
// private final AtomicInteger m_counter = new AtomicInteger();
//
// @Override
// public Thread newThread(final Runnable r) {
// Thread t = new Thread(
// r,
// "TableCellViewer-Updater-"
// + m_counter.incrementAndGet());
// t.setDaemon(true);
// return t;
// }
// });
protected int m_row = -1;
protected TableContentView m_tableContentView;
protected TableContentModel m_tableModel;
protected TableView m_tableView;
protected TabbedCellView m_cellView;
protected Map<String, Component> m_viewComponents;
protected boolean m_hiliteAdded = false;
protected final EventService m_eventService;
private PlaneSelectionEvent m_planeSel;
private int m_prevSelectedIndex = -1;
private JLabel m_statusLabel;
public TableCellViewNodeView(final T nodeModel) {
this(nodeModel, 0);
}
/**
* @param nodeModel
*/
public TableCellViewNodeView(final T nodeModel, final int portIdx) {
super(nodeModel);
m_portIdx = portIdx;
m_eventService = new EventService();
m_eventService.subscribe(this);
final JLabel load = new JLabel("Loading port content ...");
load.setPreferredSize(new Dimension(500, 500));
setComponent(load);
}
/*
* called if the selected cell changes
*/
private void cellSelectionChanged() {
// if (m_adjusting) {
// return;
// }
final int row = m_tableContentView.getSelectionModel().getLeadSelectionIndex();
final int col = m_tableContentView.getColumnModel().getSelectionModel().getLeadSelectionIndex();
cellSelectionChanged(row, col);
}
/**
* Helper function to check if a call to getValueAt(row, col) would return an IOOBEx
**/
private boolean cellExists(final int row, final int col) {
return (col >= 0 && col < m_tableModel.getColumnCount() && row >= 0 && row < m_tableModel.getRowCount());
}
/*
* called if the selected cell changes
*/
private void cellSelectionChanged(final int row, final int col) {
if (cellExists(row, col)) {
int[] cols = new int[]{col};
int[] rows = new int[]{row};
rowColIntervalSelectionChanged(rows, cols);
}
}
private void rowColIntervalSelectionChanged(final int[] rowIndices, final int[] colIndices) {
final int selection = m_cellView.getSelectedIndex();
List<Class<? extends DataValue>> prefClasses = new LinkedList<Class<? extends DataValue>>();
DataCell currentCell;
int col = -1;
int row = -1;
m_currentCells.clear();
for (int i : rowIndices) {
for (int j : colIndices) {
try {
currentCell = m_tableContentView.getContentModel().getValueAt(i, j);
} catch (final IndexOutOfBoundsException e2) {
return;
}
col = j;
row = i;
m_currentCells.add(currentCell);
prefClasses.add(currentCell.getType().getPreferredValueClass());
}
}
if (m_currentCells.size() == 0) {
return;
} else {
if (m_currentCells.size() == 1) {
m_col = col;
m_row = row;
m_eventService.publish(new TablePositionEvent(m_tableModel.getColumnCount(), m_tableModel.getRowCount(),
col + 1, row + 1, m_tableModel.getColumnName(col), m_tableModel.getRowKey(row).toString()));
} else {
}
}
m_cellView.removeAllTabs();
List<TableCellViewFactory> compatibleFactories =
TableCellViewsManager.getInstance().getCompatibleFactories(prefClasses);
List<TableCellView> availableViews = new LinkedList<TableCellView>();
if (compatibleFactories.isEmpty()) {
return;
}
for (TableCellViewFactory f : compatibleFactories) {
if (m_factoryCache.containsKey(f)) {
availableViews.addAll(m_factoryCache.get(f));
} else {
List<TableCellView> views = new LinkedList<TableCellView>(Arrays.asList(f.createTableCellViews()));
m_factoryCache.put(f, views);
availableViews.addAll(views);
}
}
for (final TableCellView v : availableViews) {
try {
m_cellView.addTab(v.getName(), v.getViewComponent());
// updateToolTips(row, col, v);
} catch (final Exception ex) {
LOGGER.error("Could not add Tab " + v.getName(), ex);
}
}
m_adjusting = true;
m_cellView.setSelectedIndex(Math.max(0, Math.min(selection, m_cellView.getTabCount() - 1)));
m_adjusting = false;
if (m_currentCells.size() == 1) {
m_adjusting = true;
m_cellView.scrollTablesToIndex(row, col);
m_adjusting = false;
} else {
m_eventService.publish(new TableOverviewDisableEvent(false, false));
}
if (getComponent() != m_cellView) {
setComponent(m_cellView);
}
}
protected void initViewComponents() {
// Initialize tableView
initializeView();
// Load data into view. Disable a waiting indicator while doing so.
final JPanel loadpanel = new JPanel();
loadpanel.setPreferredSize(new Dimension(600, 400));
// Show waiting indicator and work in background.
WaitingIndicatorUtils.setWaiting(loadpanel, true);
SwingWorker<T, Integer> worker = new SwingWorker<T, Integer>() {
@Override
protected T doInBackground() throws Exception {
m_tableContentView.setModel(m_tableModel);
return null;
}
@Override
protected void done() {
WaitingIndicatorUtils.setWaiting(loadpanel, false);
setComponent(m_tableViewPanel);
}
};
worker.execute();
while (!worker.isDone()) {
//do nothing
}
m_changeListener = new ChangeListener() {
@Override
public void stateChanged(final ChangeEvent e) {
tabSelectionChanged();
}
};
// Initialize CellView
m_cellView = new TabbedCellView(m_tableView);
m_cellView.setEventService(m_eventService);
m_cellView.addTabChangeListener(m_changeListener);
// Temporarily add loadpanel as component, so that the ui stays responsive.
setComponent(loadpanel);
}
/**
* Creates the underlying TableView and registers it to this dialog.
*/
private void initializeView() {
m_tableContentView = new TableContentView();
m_tableContentView.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
m_listSelectionListenerA = new ListSelectionListener() {
@Override
public void valueChanged(final ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
if (!m_adjusting) {
rowColIntervalSelectionChanged(m_tableContentView.getSelectedRows(),
m_tableContentView.getSelectedColumns());
}
}
};
m_tableContentView.getSelectionModel().addListSelectionListener(m_listSelectionListenerA);
m_listSelectionListenerB = new ListSelectionListener() {
@Override
public void valueChanged(final ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
if (!m_adjusting) {
rowColIntervalSelectionChanged(m_tableContentView.getSelectedRows(),
m_tableContentView.getSelectedColumns());
}
}
};
m_tableContentView.getColumnModel().getSelectionModel().addListSelectionListener(m_listSelectionListenerB);
m_tableView = new TableView(m_tableContentView);
m_tableViewPanel = new JPanel(new BorderLayout());
m_tableViewPanel.add(m_tableView, BorderLayout.CENTER);
JPanel statusBar = new JPanel();
statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED));
m_tableViewPanel.add(statusBar, BorderLayout.SOUTH);
statusBar.setPreferredSize(new Dimension(m_tableViewPanel.getWidth(), 16));
statusBar.setLayout(new BoxLayout(statusBar, BoxLayout.X_AXIS));
m_statusLabel = new JLabel(m_defStatusBarText);
m_statusLabel.setHorizontalAlignment(SwingConstants.LEFT);
statusBar.add(m_statusLabel);
m_tableView.setHiLiteHandler(getNodeModel().getInHiLiteHandler(0));
if (!m_hiliteAdded) {
getJMenuBar().add(m_tableView.createHiLiteMenu());
m_hiliteAdded = true;
}
m_cellViewCache = new HashMap<String, TableCellView>();
m_factoryCache = new HashMap<TableCellViewFactory, List<TableCellView>>();
m_viewComponents = new HashMap<String, Component>();
}
protected void loadPortContent() {
m_tableModel = new TableContentModel();
m_tableModel.setDataTable(getNodeModel().getInternalTables()[m_portIdx]);
initViewComponents();
}
/**
* {@inheritDoc}
*/
@Override
protected void modelChanged() {
if ((getNodeModel().getInternalTables() == null) || (getNodeModel().getInternalTables().length == 0)
|| (getNodeModel().getInternalTables()[m_portIdx] == null)) {
if (m_cellViewCache != null) {
for (final TableCellView v : m_cellViewCache.values()) {
v.onReset();
}
}
m_row = -1;
m_col = -1;
final JLabel nodata = new JLabel("No data table available!");
nodata.setPreferredSize(new Dimension(500, 500));
setComponent(nodata);
} else {
loadPortContent();
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onClose() {
if (m_cellViewCache != null) {
for (final TableCellView v : m_cellViewCache.values()) {
v.onClose();
}
}
if (m_tableContentView != null) {
m_tableContentView.getSelectionModel().removeListSelectionListener(m_listSelectionListenerA);
m_tableContentView.getColumnModel().getSelectionModel()
.removeListSelectionListener(m_listSelectionListenerB);
}
if (m_cellView != null) {
m_cellView.removeTabChangeListener(m_changeListener);
}
m_cellViewCache = null;
m_viewComponents = null;
m_cellView = null;
m_tableContentView = null;
m_tableModel = null;
// m_currentCell = null;
m_tableView = null;
}
/**
* {@inheritDoc}
*/
@Override
protected void onOpen() {
// NB
}
/*
* called if the selected tab changes
*/
protected void tabSelectionChanged() {
if ((m_cellView.getSelectedIndex() == -1) || (m_currentCells.size() == 0)) {
m_prevSelectedIndex = -1;
return;
}
if (m_cellView.getSelectedIndex() == m_prevSelectedIndex) {
return;
}
m_prevSelectedIndex = m_cellView.getSelectedIndex();
List<Class<? extends DataValue>> prefClasses = new LinkedList<Class<? extends DataValue>>();
for (DataCell c : m_currentCells) {
prefClasses.add(c.getType().getPreferredValueClass());
}
List<TableCellViewFactory> compatibleFactories =
TableCellViewsManager.getInstance().getCompatibleFactories(prefClasses);
int selected = m_cellView.getSelectedIndex();
int i;
for (i = 0; i < compatibleFactories.size(); ++i) {
if (m_factoryCache.get(compatibleFactories.get(i)).size() <= selected) {
selected -= m_factoryCache.get(compatibleFactories.get(i)).size();
} else {
break;
}
}
TableCellView cv = m_factoryCache.get(compatibleFactories.get(i)).get(selected);
cv.updateComponent(m_currentCells);
}
/**
* {@inheritDoc}
*/
@Override
public void setEventService(final EventService eventService) {
}
@EventListener
public void onViewerScrollEvent(final ViewerScrollEvent e) {
if (e.getDirection() == Direction.NORTH) {
cellSelectionChanged(m_row - 1, m_col);
}
if (e.getDirection() == Direction.EAST) {
cellSelectionChanged(m_row, m_col + 1);
}
if (e.getDirection() == Direction.WEST) {
cellSelectionChanged(m_row, m_col - 1);
}
if (e.getDirection() == Direction.SOUTH) {
cellSelectionChanged(m_row + 1, m_col);
}
}
@EventListener
public void onPlaneSel(final PlaneSelectionEvent e) {
m_planeSel = e;
}
@EventListener
public void onOverviewToggle(final ViewerControlEvent e) {
if (getComponent() == m_cellView) {
if (m_cellView.isTableViewVisible()) {
m_cellView.hideTableView();
}
m_tableContentView.clearSelection();
setComponent(m_tableViewPanel);
}
}
}
//}