/* * Copyright (C) 2011 Andrea Schweer * * This file is part of the Digital Parrot. * * The Digital Parrot is free software; you can redistribute it and/or modify * it under the terms of the Eclipse Public License as published by the Eclipse * Foundation or its Agreement Steward, either version 1.0 of the License, or * (at your option) any later version. * * The Digital Parrot 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 Eclipse Public License for * more details. * * You should have received a copy of the Eclipse Public License along with the * Digital Parrot. If not, see http://www.eclipse.org/legal/epl-v10.html. * */ package net.schweerelos.parrot.ui; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.RowFilter; import javax.swing.RowSorter; import javax.swing.SortOrder; import javax.swing.SwingUtilities; import javax.swing.event.EventListenerList; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; import net.schweerelos.parrot.model.NodeWrapper; import net.schweerelos.parrot.model.ParrotModel; import net.schweerelos.parrot.model.ParrotModelAdapter; import net.schweerelos.parrot.model.TableParrotModel; public class TableViewComponent extends JScrollPane implements MainViewComponent { private static final Color COLOR_GRID_LINES = UIConstants.ENVIRONMENT_LIGHT; private static final long serialVersionUID = 1L; private JTable table; private NodeWrapper selectedNode; private NodeWrapperPopupMenu popup; private TableRowSorter<TableParrotModel> rowSorter; private EventListenerList pickListeners = new EventListenerList(); public TableViewComponent() { super(); setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12), BorderFactory.createLoweredBevelBorder())); table = new JTable(); table.setRowSelectionAllowed(false); table.setColumnSelectionAllowed(false); table.setCellSelectionEnabled(true); table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); ListSelectionListener selectionListener = new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; } ListSelectionModel rowSelections = table.getSelectionModel(); ListSelectionModel columnSelections = table.getColumnModel().getSelectionModel(); if (rowSelections.isSelectionEmpty() || columnSelections.isSelectionEmpty()) { // no new selection -> nothing has changed return; } int rowIndex = rowSelections.getLeadSelectionIndex(); int columnIndex = columnSelections.getLeadSelectionIndex(); int modelRowIndex = table.convertRowIndexToModel(rowIndex); int modelColumnIndex = table.convertColumnIndexToModel(columnIndex); if (selectedNode != null) { selectedNode.setHereTooSelected(false); } final NodeWrapper newSelection = (NodeWrapper) table.getModel().getValueAt(modelRowIndex, modelColumnIndex); newSelection.setHereTooSelected(true); selectedNode = newSelection; table.invalidate(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { table.repaint(); } }); } }; table.getSelectionModel().addListSelectionListener(selectionListener); table.getColumnModel().getSelectionModel().addListSelectionListener(selectionListener); table.setAutoCreateRowSorter(false); table.setFillsViewportHeight(true); table.setGridColor(COLOR_GRID_LINES); table.setShowGrid(false); table.setShowHorizontalLines(true); // 0px between cells in the same row; 1px between rows table.setIntercellSpacing(new Dimension(0, 1)); FontMetrics metrics = table.getFontMetrics(table.getFont()); int rowHeight = 2 * metrics.getMaxAscent() + 2 * metrics.getMaxDescent() + metrics.getLeading(); table.setRowHeight(rowHeight); setViewportView(table); // context menus + double-click for chain nav table.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() != 2) { return; } NodeWrapper node = getNodeWrapperAtPoint(e); if (node != null) { fireNodeSelected(node); } } @Override public void mousePressed(MouseEvent e) { handlePopup(e); } @Override public void mouseReleased(MouseEvent e) { handlePopup(e); } private void handlePopup(MouseEvent e) { if (!e.isPopupTrigger()) { return; } NodeWrapper node = getNodeWrapperAtPoint(e); if (node != null) { popup.setNodeWrapper(node); popup.show(table, e.getX(), e.getY()); } } private NodeWrapper getNodeWrapperAtPoint(MouseEvent e) { int viewColumn = table.columnAtPoint(e.getPoint()); int viewRow = table.rowAtPoint(e.getPoint()); if (viewColumn == -1 || viewRow == -1) { // this means that we have an invalid point return null; } NodeWrapper node = (NodeWrapper) table.getValueAt(viewRow, viewColumn); return node; } }); } @Override public void setModel(ParrotModel model) { if (model == null) { return; } model.addParrotModelListener(new TableViewModelListener()); TableModel tableModel = ((TableParrotModel) model).asTableModel(); table.setModel(tableModel); rowSorter = new TableRowSorter<TableParrotModel>((TableParrotModel) tableModel); List <RowSorter.SortKey> sortKeys = new ArrayList<RowSorter.SortKey>(); sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING)); sortKeys.add(new RowSorter.SortKey(1, SortOrder.ASCENDING)); rowSorter.setSortKeys(sortKeys); table.setRowSorter(rowSorter); popup = new NodeWrapperPopupMenu(SwingUtilities.getRoot(this), model); TableCellRenderer cellRenderer = new NodeWrapperRenderer(model); table.setDefaultRenderer(NodeWrapper.class, cellRenderer); } @Override public JComponent asJComponent() { return this; } private final class TableViewModelListener extends ParrotModelAdapter { @Override public void restrictionsChanged(final Collection<NodeWrapper> currentlyHidden) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { rowSorter.setRowFilter(new RowFilter<TableParrotModel, Integer>() { @Override public boolean include(Entry<? extends TableParrotModel, ? extends Integer> entry) { if (currentlyHidden == null || currentlyHidden.isEmpty()) { return true; } // entry represents a statement triple // only include entry (=row) if none of its parts is included in currentlyHidden for (int i = 0; i < entry.getValueCount(); i++) { Object value = entry.getValue(i); if (value instanceof NodeWrapper && currentlyHidden.contains(value)) { // we found a part that is supposed to be hidden return false; } } return true; } }); table.repaint(); } }); } @Override public void highlightsChanged() { SwingUtilities.invokeLater(new Runnable() { public void run() { table.invalidate(); table.repaint(); } }); } @Override public void modelIdle() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { table.setEnabled(true); table.setCursor(Cursor.getDefaultCursor()); } }); } @Override public void modelBusy() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { table.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); table.setEnabled(false); } }); } } private void fireNodeSelected(NodeWrapper newSelection) { // from EventListenerList javadocs (minus the bugs) PickListener[] listeners = pickListeners .getListeners(PickListener.class); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 1; i >= 0; i--) { listeners[i].picked(newSelection); } } @Override public Collection<NodeWrapper> getSelectedNodes() { ArrayList<NodeWrapper> result = new ArrayList<NodeWrapper>(1); result.add(selectedNode); return result; } @Override public void addPickListener(PickListener listener) { pickListeners.add(PickListener.class, listener); } @Override public void removePickListener(PickListener listener) { pickListeners.remove(PickListener.class, listener); } @Override public String getTitle() { return "List"; } }