/* * 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; }