/*
* Copyright 2007 - 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.jailer.ui;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.JLabel;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import net.sf.jailer.datamodel.DataModel;
import net.sf.jailer.datamodel.Table;
import net.sf.jailer.util.CancellationException;
import net.sf.jailer.util.CancellationHandler;
import net.sf.jailer.util.CycleFinder;
/**
* Shows dependency cycles.
*
* @author Ralf Wisser
*/
public class CyclesView extends javax.swing.JDialog {
/**
* Maximum number of tables in a view's line.
*/
private final static int MAX_TABLES_PER_LINE = 4;
/**
* The extraction model frame.
*/
private final ExtractionModelFrame extractionModelFrame;
/**
* Currently selected table.
*/
private String selectedTable;
/**
* Background colors per row.
*/
private final List<Color> bgColor = new ArrayList<Color>();
/**
* Holds infos about a cell in the view.
*/
private class CellInfo {
public int row, column;
boolean arrowLeft, arrowRight;
};
/**
* Holds infos about a cell in the view.
*/
private Set<CellInfo> cellInfo = new HashSet<CellInfo>();
/** Creates new form FindDialog */
public CyclesView(ExtractionModelFrame extractionModelFrame) {
super(extractionModelFrame, false);
this.extractionModelFrame = extractionModelFrame;
initComponents();
cyclesTable = new JTable() {
private static final long serialVersionUID = 8960056200057023368L;
/**
* Paints selected path.
*/
@Override
public void paint(Graphics graphics) {
super.paint(graphics);
if (!(graphics instanceof Graphics2D)) return;
Graphics2D g2d = (Graphics2D) graphics;
for (CellInfo posInfo: cellInfo) {
Rectangle r = cyclesTable.getCellRect(posInfo.row, posInfo.column, false);
Color color = new Color(255, 0, 0, 150);
g2d.setColor(color);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(2));
int a = r.height / 4;
int w = 3 * a;
if (posInfo.arrowLeft) {
int x = r.x - w - a * 2;
int y = r.y + r.height / 2;
g2d.drawLine(x, y, x + w, y);
g2d.drawLine(x + w - 1, y, x + w - a, y - a);
g2d.drawLine(x + w - 1, y, x + w - a, y + a);
}
if (posInfo.arrowRight) {
int x = r.x + r.width - w - a * 2;
int y = r.y + r.height / 2;
g2d.drawLine(x, y, x + w, y);
g2d.drawLine(x + w - 1, y, x + w - a, y - a);
g2d.drawLine(x + w - 1, y, x + w - a, y + a);
}
}
}
};
cyclesTable.setShowGrid(false);
cyclesTable.setSurrendersFocusOnKeystroke(true);
jScrollPane1.setViewportView(cyclesTable);
jScrollPane1.setColumnHeaderView(null);
cyclesTable.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent e) {
// context menu
if (SwingUtilities.isRightMouseButton(e)) {
int row = cyclesTable.rowAtPoint(e.getPoint());
int column = cyclesTable.columnAtPoint(e.getPoint());
if (row < 0 || column < 0) return;
Object value = cyclesTable.getModel().getValueAt(row, column);
if (value == null || !(value instanceof String)) return;
Table table = getDataModel().getTableByDisplayName((String) value);
if (table != null) {
JPopupMenu popup = CyclesView.this.extractionModelFrame.extractionModelEditor.graphView.createPopupMenu(table, false);
UIUtil.fit(popup);
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
});
final TableCellRenderer defaultTableCellRenderer = cyclesTable.getDefaultRenderer(String.class);
cyclesTable.setDefaultRenderer(Object.class, new TableCellRenderer() {
private Font font = new JLabel("normal").getFont();
private Font normal = new Font(font.getName(), font.getStyle() & ~Font.BOLD, font.getSize());
private Font bold = new Font(font.getName(), font.getStyle() | Font.BOLD, font.getSize());
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
isSelected = selectedTable != null && selectedTable.equals(value);
if (value == null || column < 1 || "".equals(value)) {
hasFocus = false;
}
Component render = defaultTableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (render instanceof JLabel && !isSelected && row < bgColor.size()) {
((JLabel) render).setBackground(bgColor.get(row));
}
if (render instanceof JLabel) {
((JLabel) render).setForeground(Color.BLACK);
((JLabel) render).setFont(normal);
((JLabel) render).setToolTipText(((JLabel) render).getText());
if (selectedTable != null) {
if (selectedTable.equals(value)) {
((JLabel) render).setFont(bold);
}
}
}
return render;
}
});
cyclesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
cyclesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent evt) {
int col = cyclesTable.getSelectedColumn();
int row = cyclesTable.getSelectedRow();
if (col >= 1 && row >= 0) {
String displayName = (String) cyclesTable.getModel().getValueAt(row, col);
cyclesTable.getSelectionModel().clearSelection();
if (displayName != null && !"".equals(displayName)) {
// if (selectedTable == null || !selectedTable.equals(displayName)) {
selectedTable = displayName;
repaint();
Table table = getDataModel().getTableByDisplayName(selectedTable);
if (table != null) {
if (!CyclesView.this.extractionModelFrame.extractionModelEditor.select(table)) {
CyclesView.this.extractionModelFrame.extractionModelEditor.setRootSelection(table);
}
}
// }
}
}
}
});
setLocation(500, 150);
setSize(600, 400);
// setAlwaysOnTop(true);
}
private class FindCyclesDialog extends javax.swing.JDialog {
public FindCyclesDialog() {
super(extractionModelFrame, false);
initComponents();
UIUtil.initPeer();
}
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
label = new javax.swing.JLabel();
jButton1 = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
getContentPane().setLayout(new java.awt.GridBagLayout());
label.setText(" Finding cycles... ");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.gridwidth = 2;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(12, 12, 12, 12);
getContentPane().add(label, gridBagConstraints);
jButton1.setText(" Abort ");
jButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 10;
gridBagConstraints.gridwidth = 2;
gridBagConstraints.insets = new java.awt.Insets(12, 0, 12, 0);
getContentPane().add(jButton1, gridBagConstraints);
pack();
}// </editor-fold>//GEN-END:initComponents
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
this.setVisible(false);
CancellationHandler.cancel(null);
}//GEN-LAST:event_jButton1ActionPerformed
private javax.swing.JButton jButton1;
private javax.swing.JLabel label;
private static final long serialVersionUID = -6499791486275376059L;
}
public void findCycles() {
final FindCyclesDialog findCyclesDialog = new FindCyclesDialog();
findCyclesDialog.setLocation(getLocation().x + getSize().width / 2 - findCyclesDialog.getSize().width / 2,
getLocation().y + getSize().height / 2 - findCyclesDialog.getSize().height / 2);
CancellationHandler.reset(null);
new Thread() {
@Override
public void run() {
try {
final Collection<CycleFinder.Path> cycles = CycleFinder.findCycle(extractionModelFrame.extractionModelEditor.dataModel, extractionModelFrame.extractionModelEditor.dataModel.getTables());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
findCyclesDialog.setVisible(false);
CyclesView.this.setVisible(true);
refreshTableModel(cycles);
}
});
} catch (CancellationException ce) {
CancellationHandler.reset(null);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
CyclesView.this.setVisible(false);
}
});
} catch (final Throwable t) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
UIUtil.showException(CyclesView.this, "Error", t);
CyclesView.this.setVisible(false);
}
});
}
}
}.start();
findCyclesDialog.setVisible(true);
}
/**
* Gets current data model.
*
* @return current data model
*/
private DataModel getDataModel() {
return extractionModelFrame.extractionModelEditor.dataModel;
}
/**
* Refreshes the dialog after the model has been changed.
*/
public void refresh() {
}
/**
* Refreshes the table model.
*/
private synchronized void refreshTableModel(Collection<CycleFinder.Path> cycles) {
cellInfo.clear();
Set<Table> tables = new HashSet<Table>();
for (CycleFinder.Path p: cycles) {
List<Table> currentLineT = new ArrayList<Table>();
p.fillPath(currentLineT);
for (Table t: currentLineT) {
tables.add(t);
}
}
numLabel.setText(" " + cycles.size() + " Cycles, " + tables.size() + " distinct tables");
Object[] columns = new Object[MAX_TABLES_PER_LINE + 1];
for (int i = 0; i < columns.length; ++i) {
columns[i] = "";
}
columns[0] = "Distance";
columns[1] = "Table";
List<Object[]> data = new ArrayList<Object[]>();
int distance = 0;
final Color BG1 = new Color(255, 255, 255);
final Color BG2 = new Color(230, 255, 255);
bgColor.clear();
for (CycleFinder.Path cycle: cycles) {
// add current line to table model
List<Table> currentLineT = new ArrayList<Table>();
cycle.fillPath(currentLineT);
List<String> currentLine = new ArrayList<String>();
for (Table t: currentLineT) {
currentLine.add(extractionModelFrame.extractionModelEditor.dataModel.getDisplayName(t));
}
Object[] lineAsObjects = new Object[MAX_TABLES_PER_LINE + 1];
Arrays.fill(lineAsObjects, "");
int col = 0;
lineAsObjects[col++] = "";
CellInfo cellInfo = null;
for (String t: currentLine) {
cellInfo = new CellInfo();
cellInfo.arrowLeft = false;
cellInfo.arrowRight = true;
this.cellInfo.add(cellInfo);
if (col <= MAX_TABLES_PER_LINE) {
cellInfo.column = col;
lineAsObjects[col++] = t;
} else {
data.add(lineAsObjects);
bgColor.add(distance % 2 == 0? BG1 : BG2);
lineAsObjects = new Object[MAX_TABLES_PER_LINE + 1];
Arrays.fill(lineAsObjects, "");
col = 1;
cellInfo.arrowLeft = true;
cellInfo.column = col;
lineAsObjects[col++] = t;
}
cellInfo.row = data.size();
}
if (cellInfo != null) {
cellInfo.arrowRight = false;
}
if (col > 1) {
data.add(lineAsObjects);
bgColor.add(distance % 2 == 0? BG1 : BG2);
}
lineAsObjects = new Object[MAX_TABLES_PER_LINE + 1];
Arrays.fill(lineAsObjects, "");
data.add(lineAsObjects);
bgColor.add(distance % 2 == 0? BG1 : BG2);
++distance;
}
Object[][] dataArray = (Object[][]) data.toArray(new Object[data.size()][]);
DefaultTableModel tableModel = new DefaultTableModel(dataArray, columns) {
public boolean isCellEditable(int row, int column) {
return false;
}
private static final long serialVersionUID = -6639310191624899380L;
};
cyclesTable.setModel(tableModel);
for (int i = 0; i < cyclesTable.getColumnCount(); i++) {
TableColumn column = cyclesTable.getColumnModel().getColumn(i);
int width = 1;
Component comp = cyclesTable.getDefaultRenderer(String.class).
getTableCellRendererComponent(
cyclesTable, column.getHeaderValue(),
false, false, 0, i);
width = Math.max(width, comp.getPreferredSize().width);
for (int line = 0; line < dataArray.length; ++line) {
comp = cyclesTable.getDefaultRenderer(String.class).
getTableCellRendererComponent(
cyclesTable, dataArray[line][i],
false, false, line, i);
width = Math.max(width, comp.getPreferredSize().width);
}
if (i == 0) {
width = 50;
column.setMaxWidth(width);
}
column.setPreferredWidth(width);
}
cyclesTable.setIntercellSpacing(new Dimension(0, 0));
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc=" Erzeugter Quelltext ">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
jPanel1 = new javax.swing.JPanel();
numLabel = new javax.swing.JLabel();
jScrollPane1 = new javax.swing.JScrollPane();
cyclesTable = new javax.swing.JTable();
getContentPane().setLayout(new java.awt.GridBagLayout());
setTitle("Cycle View");
jPanel1.setLayout(new java.awt.GridBagLayout());
numLabel.setText(" ");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 10;
gridBagConstraints.gridy = 10;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
jPanel1.add(numLabel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 20;
gridBagConstraints.gridy = 10;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 2);
cyclesTable.setTableHeader(null);
cyclesTable.setModel(new javax.swing.table.DefaultTableModel(
new Object [][] {
{null, null, null, null},
{null, null, null, null},
{null, null, null, null},
{null, null, null, null}
},
new String [] {
"Titel 1", "Titel 2", "Titel 3", "Titel 4"
}
));
cyclesTable.setShowGrid(false);
cyclesTable.setSurrendersFocusOnKeystroke(true);
jScrollPane1.setViewportView(cyclesTable);
jScrollPane1.setColumnHeaderView(null);
jScrollPane1.setColumnHeader(null);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 10;
gridBagConstraints.gridy = 20;
gridBagConstraints.gridwidth = 30;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 2);
jPanel1.add(jScrollPane1, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
getContentPane().add(jPanel1, gridBagConstraints);
pack();
}// </editor-fold>//GEN-END:initComponents
// Variablendeklaration - nicht modifizieren//GEN-BEGIN:variables
private javax.swing.JTable cyclesTable;
private javax.swing.JLabel numLabel;
private javax.swing.JPanel jPanel1;
private javax.swing.JScrollPane jScrollPane1;
// Ende der Variablendeklaration//GEN-END:variables
private static final long serialVersionUID = 5485949274233292142L;
}