/* * 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.graphical_view; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.ButtonGroup; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.JSeparator; import javax.swing.SwingUtilities; import net.sf.jailer.datamodel.Association; import net.sf.jailer.datamodel.DataModel; import net.sf.jailer.datamodel.Table; import net.sf.jailer.subsetting.ScriptFormat; import net.sf.jailer.ui.ExtractionModelEditor; import net.sf.jailer.ui.QueryBuilderDialog; import net.sf.jailer.ui.scrollmenu.JScrollMenu; import net.sf.jailer.ui.scrollmenu.JScrollPopupMenu; import net.sf.jailer.util.LayoutStorage; import prefuse.Display; import prefuse.Visualization; import prefuse.action.Action; import prefuse.action.ActionList; import prefuse.action.RepaintAction; import prefuse.action.assignment.ColorAction; import prefuse.action.filter.GraphDistanceFilter; import prefuse.action.layout.graph.ForceDirectedLayout; import prefuse.activity.Activity; import prefuse.controls.Control; import prefuse.controls.DragControl; import prefuse.controls.FocusControl; import prefuse.controls.ToolTipControl; import prefuse.controls.WheelZoomControl; import prefuse.controls.ZoomToFitControl; import prefuse.data.Edge; import prefuse.data.Graph; import prefuse.data.Node; import prefuse.data.Schema; import prefuse.data.Tuple; import prefuse.data.event.TupleSetListener; import prefuse.data.expression.BooleanLiteral; import prefuse.data.tuple.TupleSet; import prefuse.render.Renderer; import prefuse.render.RendererFactory; import prefuse.render.ShapeRenderer; import prefuse.util.ColorLib; import prefuse.util.GraphicsLib; import prefuse.util.display.DisplayLib; import prefuse.util.force.Force; import prefuse.util.force.ForceItem; import prefuse.util.force.ForceSimulator; import prefuse.util.force.Integrator; import prefuse.util.force.NBodyForce; import prefuse.util.ui.UILib; import prefuse.visual.EdgeItem; import prefuse.visual.NodeItem; import prefuse.visual.VisualGraph; import prefuse.visual.VisualItem; /** * Graphical restriction model view and editor. * * @author Ralf Wisser */ public class GraphicalDataModelView extends JPanel { /** * Maximum number of tables to make visible during expansion. */ private final int EXPAND_LIMIT = 50; /** * The selected association. */ public Association selectedAssociation; /** * Set of names of all tables on path from selected one to the root. */ Set<String> tablesOnPath = new HashSet<String>(); /** * Path from selected one to the root. */ private List<Association> associationsOnPath = new ArrayList<Association>(); // constants private static final String graph = "graph"; private static final String nodes = "graph.nodes"; private static final String edges = "graph.edges"; // prefuse visualization private Visualization visualization; private VisualGraph visualGraph; public Display display; private boolean inInitialization = false; /** * The root table. */ final Table root; /** * Table renderer. */ private TableRenderer tableRenderer; /** * Association renderer. */ private CompositeAssociationRenderer associationRenderer; /** * The enclosing model editor. */ public final ExtractionModelEditor modelEditor; /** * Layouts the model graph. */ private ForceDirectedLayout layout; /** * Zooms to fit on mouse click. */ private ZoomToFitControlExtension zoomToFitControl; /** * Table details mode. */ private boolean showTableDetails; private NBodyForce force; private volatile boolean layoutHasBeenSet = false; /** * Constructor. * * @param model the restricted data model * @param modelEditor enclosing model editor * @param width * @param height initial size */ public GraphicalDataModelView(final DataModel model, ExtractionModelEditor modelEditor, Table subject, boolean expandSubject, int width, int height) { super(new BorderLayout()); this.model = model; this.modelEditor = modelEditor; this.root = subject; tableRenderer = new TableRenderer(model, this); final Set<Table> initiallyVisibleTables = new HashSet<Table>(); if (subject != null) { Map<String, double[]> positions = LayoutStorage.getPositions(subject.getName()); if (positions != null) { for (String tn: positions.keySet()) { Table table = model.getTable(tn); if (table != null && !table.equals(subject)) { initiallyVisibleTables.add(table); } } } } theGraph = getModelGraph(model, initiallyVisibleTables, expandSubject); // create a new, empty visualization for our data visualization = new Visualization(); final ZoomBoxControl zoomBoxControl = new ZoomBoxControl(); // -------------------------------------------------------------------- // set up the renderers final ShapeRenderer sr = new ShapeRenderer() { protected Shape getRawShape(VisualItem item) { item.setFillColor(ColorLib.rgb(220,210,0)); double x = item.getX(); if ( Double.isNaN(x) || Double.isInfinite(x) ) x = 0; double y = item.getY(); if ( Double.isNaN(y) || Double.isInfinite(y) ) y = 0; double width = 10 * item.getSize(); // Center the shape around the specified x and y if ( width > 1 ) { x = x-width/2; y = y-width/2; } return ellipse((float) x, (float) y, (float) width, (float) width); } }; associationRenderer = new CompositeAssociationRenderer(); tableRenderer.setRoundedCorner(3, 3); tableRenderer.setVerticalPadding(3); tableRenderer.setHorizontalPadding(3); visualization.setRendererFactory(new RendererFactory() { public Renderer getRenderer(VisualItem item) { if (zoomBoxControl.getRenderer().isBoxItem(item)) { return zoomBoxControl.getRenderer(); } if (item instanceof EdgeItem) { return associationRenderer; } if (item.get("association") != null) { return sr; } return tableRenderer; } }); // adds graph to visualization and sets renderer label field setGraph(theGraph); // fix selected focus nodes TupleSet focusGroup = visualization.getGroup(Visualization.FOCUS_ITEMS); focusGroup.addTupleSetListener(new TupleSetListener() { public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem) { boolean draw = false; if (inInitialization) { return; } for (int i=0; i<add.length; ++i ) { if (add[i] instanceof NodeItem) { draw = true; // expandTable(theGraph, model.getTable(add[i].getString("label"))); ((VisualItem)add[i]).setFixed(false); ((VisualItem)add[i]).setFixed(true); } } if (draw) { // m_vis.run("draw"); } } }); // -------------------------------------------------------------------- // create actions to process the visual data int hops = 30; final GraphDistanceFilter filter = new GraphDistanceFilter(graph, hops); ColorAction fill = new ColorAction(nodes, VisualItem.FILLCOLOR, ColorLib.rgba(255,235,20,100)); fill.add(VisualItem.FIXED, ColorLib.rgba(255,235,20,100)); fill.add(VisualItem.HIGHLIGHT, ColorLib.rgba(160,160,0,120)); ActionList draw = new ActionList(); draw.add(filter); draw.add(fill); draw.add(new ColorAction(nodes, VisualItem.STROKECOLOR, 0)); draw.add(new ColorAction(nodes, VisualItem.TEXTCOLOR, ColorLib.rgb(0,0,0))); draw.add(new ColorAction(edges, VisualItem.FILLCOLOR, ColorLib.gray(200))); draw.add(new ColorAction(edges, VisualItem.STROKECOLOR, ColorLib.gray(200))); ActionList animate = new ActionList(Activity.INFINITY); animate.add(fill); animate.add(new RepaintAction()); if (modelEditor.extractionModelFrame.animationStepTime > 0) { animate.setStepTime(modelEditor.extractionModelFrame.animationStepTime); } // finally, we register our ActionList with the Visualization. // we can later execute our Actions by invoking a method on our // Visualization, using the name we've chosen below. visualization.putAction("draw", draw); visualization.putAction("layout", animate); visualization.runAfter("draw", "layout"); // -------------------------------------------------------------------- // set up a display to show the visualization display = new Display(visualization); display.setSize(width, height); display.pan(width / 2, height / 2); display.setForeground(Color.GRAY); display.setBackground(Color.WHITE); // main display controls display.addControlListener(new FocusControl(1)); display.addControlListener(new DragControl() { @Override public void itemClicked(VisualItem item, MouseEvent e) { Table table = model.getTable(item.getString("label")); if (SwingUtilities.isLeftMouseButton(e)) { if (table != null && e.getClickCount() == 1) { selectTable(table); } } // context menu if (SwingUtilities.isRightMouseButton(e)) { Association association = (Association) item.get("association"); if (association != null) { if (Boolean.TRUE.equals(item.get("full"))) { associationRenderer.useAssociationRendererForLocation = true; VisualItem findItem = display.findItem(e.getPoint()); if (findItem == null || !findItem.equals(item)) { association = association.reversalAssociation; } associationRenderer.useAssociationRendererForLocation = false; } JPopupMenu popup = createPopupMenu(association); popup.show(e.getComponent(), e.getX(), e.getY()); } if (table != null) { JPopupMenu popup = createPopupMenu(table, true); popup.show(e.getComponent(), e.getX(), e.getY()); } } super.itemClicked(item, e); } public void itemPressed(VisualItem item, MouseEvent e) { if (UILib.isButtonPressed(e, LEFT_MOUSE_BUTTON)) { Association association = (Association) item.get("association"); if (association != null) { if (Boolean.TRUE.equals(item.get("full"))) { associationRenderer.useAssociationRendererForLocation = true; VisualItem findItem = display.findItem(e.getPoint()); if (findItem == null || !findItem.equals(item)) { association = association.reversalAssociation; } associationRenderer.useAssociationRendererForLocation = false; } setSelection(association); } Table table = model.getTable(item.getString("label")); if (table != null && e.getClickCount() > 1) { GraphicalDataModelView.this.modelEditor.captureLayout(); try { if (expandedTables.contains(table)) { collapseTable(theGraph, table, false); display.pan(1, 0); display.pan(0, 1); Association sa = selectedAssociation; setSelection(null); setSelection(sa); visualization.invalidateAll(); display.invalidate(); } else { expandTable(theGraph, table); visualization.invalidateAll(); display.invalidate(); } } finally { GraphicalDataModelView.this.modelEditor.checkLayoutStack(); } } } super.itemPressed(item, e); } public void itemReleased(VisualItem item, MouseEvent e) { // fix after drag super.itemReleased(item, e); if (!SwingUtilities.isLeftMouseButton(e)) return; if (item instanceof NodeItem) { item.setFixed(true); } } }); display.addControlListener(new PanControl()); display.addControlListener(zoomBoxControl); display.addControlListener(new WheelZoomControl(){ /** * @see java.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event.MouseWheelEvent) */ public void mouseWheelMoved(MouseWheelEvent e) { Display display = (Display)e.getComponent(); Point p = e.getPoint(); zoom(display, p, 1 + 0.1f * e.getWheelRotation(), false); } }); zoomToFitControl = new ZoomToFitControlExtension(Visualization.ALL_ITEMS, 50, 800, Control.RIGHT_MOUSE_BUTTON, model); display.addControlListener(zoomToFitControl); display.addControlListener(new ToolTipControl("tooltip")); display.setForeground(Color.GRAY); display.setBackground(Color.WHITE); // now we run our action list visualization.run("draw"); resetExpandedState(); display.setHighQuality(true); add(display); Rectangle2D bounds = null; if (root != null) { synchronized (visualization) { Iterator items = visualization.items(BooleanLiteral.TRUE); for (int m_visibleCount=0; items.hasNext(); ++m_visibleCount ) { VisualItem item = (VisualItem)items.next(); if (item.canGetString("label") ) { String tableName = item.getString("label"); double[] pos = LayoutStorage.getPosition(root.getName(), tableName); if (pos != null) { if (bounds == null) { bounds = new Rectangle2D.Double(pos[0], pos[1], 1, 1); } else { bounds.add(new Point2D.Double(pos[0], pos[1])); } } } } } } if (bounds != null) { display.panToAbs(new Point2D.Double(bounds.getCenterX(), bounds.getCenterY())); } layout = new ForceDirectedLayout(graph) { protected float getMassValue(VisualItem n) { return zoomBoxControl.getRenderer().isBoxItem(n)? 0.01f : showTableDetails? 2.0f : 1.0f; } }; for (Force force: layout.getForceSimulator().getForces()) { if (force instanceof NBodyForce) { this.force = (NBodyForce) force; } } if (force != null) { force.setParameter(NBodyForce.GRAVITATIONAL_CONST, -100.0f); } updateTableDetailsMode(); layout.getForceSimulator().setIntegrator(new Integrator() { public void integrate(ForceSimulator sim, long timestep) { float speedLimit = sim.getSpeedLimit(); float vx, vy, v, coeff; float[][] k, l; Iterator iter = sim.getItems(); while ( iter.hasNext() ) { ForceItem item = (ForceItem)iter.next(); coeff = timestep / item.mass; k = item.k; l = item.l; item.plocation[0] = item.location[0]; item.plocation[1] = item.location[1]; k[0][0] = timestep*item.velocity[0]; k[0][1] = timestep*item.velocity[1]; l[0][0] = coeff*item.force[0]; l[0][1] = coeff*item.force[1]; // Set the position to the new predicted position item.location[0] += 0.5f*k[0][0]; item.location[1] += 0.5f*k[0][1]; } // recalculate forces sim.accumulate(); iter = sim.getItems(); while ( iter.hasNext() ) { ForceItem item = (ForceItem)iter.next(); coeff = timestep / item.mass; k = item.k; l = item.l; vx = item.velocity[0] + .5f*l[0][0]; vy = item.velocity[1] + .5f*l[0][1]; v = (float)Math.sqrt(vx*vx+vy*vy); if ( v > speedLimit ) { vx = speedLimit * vx / v; vy = speedLimit * vy / v; } k[1][0] = timestep*vx; k[1][1] = timestep*vy; l[1][0] = coeff*item.force[0]; l[1][1] = coeff*item.force[1]; // Set the position to the new predicted position item.location[0] = item.plocation[0] + 0.5f*k[1][0]; item.location[1] = item.plocation[1] + 0.5f*k[1][1]; } // recalculate forces sim.accumulate(); iter = sim.getItems(); while ( iter.hasNext() ) { ForceItem item = (ForceItem)iter.next(); coeff = timestep / item.mass; k = item.k; l = item.l; vx = item.velocity[0] + .5f*l[1][0]; vy = item.velocity[1] + .5f*l[1][1]; v = (float)Math.sqrt(vx*vx+vy*vy); if ( v > speedLimit ) { vx = speedLimit * vx / v; vy = speedLimit * vy / v; } k[2][0] = timestep*vx; k[2][1] = timestep*vy; l[2][0] = coeff*item.force[0]; l[2][1] = coeff*item.force[1]; // Set the position to the new predicted position item.location[0] = item.plocation[0] + 0.5f*k[2][0]; item.location[1] = item.plocation[1] + 0.5f*k[2][1]; } // recalculate forces sim.accumulate(); iter = sim.getItems(); while ( iter.hasNext() ) { ForceItem item = (ForceItem)iter.next(); coeff = timestep / item.mass; k = item.k; l = item.l; float[] p = item.plocation; vx = item.velocity[0] + l[2][0]; vy = item.velocity[1] + l[2][1]; v = (float)Math.sqrt(vx*vx+vy*vy); if ( v > speedLimit ) { vx = speedLimit * vx / v; vy = speedLimit * vy / v; } k[3][0] = timestep*vx; k[3][1] = timestep*vy; l[3][0] = coeff*item.force[0]; l[3][1] = coeff*item.force[1]; float dx = (k[0][0]+k[3][0])/6.0f + (k[1][0]+k[2][0])/3.0f; float dy = (k[0][1]+k[3][1])/6.0f + (k[1][1]+k[2][1])/3.0f; if (dx*dx+dy*dy < 3) { dx = dy = 0; } item.location[0] = p[0] + dx; item.location[1] = p[1] + dy; vx = (l[0][0]+l[3][0])/6.0f + (l[1][0]+l[2][0])/3.0f; vy = (l[0][1]+l[3][1])/6.0f + (l[1][1]+l[2][1])/3.0f; v = (float)Math.sqrt(vx*vx+vy*vy); if ( v > speedLimit ) { vx = speedLimit * vx / v; vy = speedLimit * vy / v; } item.velocity[0] += vx; item.velocity[1] += vy; } } }); layout.setVisualization(visualization); animate.add(layout); layout.run(); if (root != null) { final Map<String, double[]> posMap = LayoutStorage.getPositions(root.getName()); Action a = new Action() { boolean done = false; @Override public void run(double frac) { if (!done) { synchronized (visualization) { if (root != null && !initiallyVisibleTables.isEmpty()) { Iterator items = visualization.items(BooleanLiteral.TRUE); for (int m_visibleCount=0; items.hasNext(); ++m_visibleCount ) { VisualItem item = (VisualItem)items.next(); if (item.canGetString("label") ) { String tableName; tableName = item.getString("label"); double[] pos = posMap.get(tableName); if (pos != null) { item.setX(pos[0]); item.setY(pos[1]); item.setEndX(pos[0]); item.setEndY(pos[1]); item.setFixed(pos[2] == 1.0); } } } } } layout.reset(); visualization.invalidateAll(); done = true; layoutHasBeenSet = true; } } }; a.alwaysRunAfter(layout); animate.add(a); } animate.add(new Action() { // force redraw, work-around for a strange repaint bug long startTime = System.currentTimeMillis(); boolean done = false; @Override public void run(double frac) { if (!done && System.currentTimeMillis() > startTime + 30) { display.pan(1, 1); display.pan(-1, -1); done = true; } } }); } /** * Stores current positions of the tables. */ @SuppressWarnings("unchecked") public void storeLayout() { if (root != null && layoutHasBeenSet) { synchronized (visualization) { LayoutStorage.removeAll(root.getName()); Iterator items = visualization.items(BooleanLiteral.TRUE); for (int m_visibleCount=0; items.hasNext(); ++m_visibleCount ) { VisualItem item = (VisualItem)items.next(); if (item.canGetString("label") ) { String tableName; tableName = item.getString("label"); if (tableName != null) { LayoutStorage.setPosition(root.getName(), tableName, new double[] { item.getX(), item.getY(), item.isFixed()? 1.0:0.0 }); } } } LayoutStorage.checkSignificance(root.getName()); } } } /** * Creates popup menu. * * @param table the table for which the menu pops up * @return the popup menu */ public JPopupMenu createPopupMenu(final Table table, boolean withNavigation) { JPopupMenu popup = new JScrollPopupMenu(); JMenu navigateTo = null; if (withNavigation) { navigateTo = new JScrollMenu("Show Associated Table"); List<Association> aList = new ArrayList<Association>(); Set<Table> includedTables = new HashSet<Table>(); Set<Association> visualizable = new HashSet<Association>(); for (Association a: table.associations) { if (!includedTables.contains(a.destination)) { if (isVisualizable(a)) { visualizable.add(a); } aList.add(a); includedTables.add(a.destination); } } Collections.sort(aList, new Comparator<Association>() { @Override public int compare(Association o1, Association o2) { return o1.getDataModel().getDisplayName(o1.destination) .compareTo(o2.getDataModel().getDisplayName(o2.destination)); } }); navigateTo.setEnabled(false); JMenu currentMenu = navigateTo; // int numItems = 0; // final int MAX_ITEMS = 30; Font italic = null; for (final Association a: aList) { String miText = a.getDataModel().getDisplayName(a.destination); JMenuItem mi = new JMenuItem(); if (!visualizable.contains(a)) { if (italic == null) { Font font = new JLabel().getFont(); italic = new Font(font.getName(), font.getStyle() | Font.ITALIC, font.getSize()); } mi.setFont(italic); } final int MAX_LENGTH = 50; if (miText.length() < MAX_LENGTH) { mi.setText(miText); } else { mi.setText(miText.substring(0, MAX_LENGTH) + "..."); mi.setToolTipText(miText); } mi.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { GraphicalDataModelView.this.modelEditor.select(a); } }); if (renderedAssociations.containsKey(a)|| renderedAssociations.containsKey(a.reversalAssociation)) { mi.setEnabled(false); } else { navigateTo.setEnabled(true); } // if (numItems++ > MAX_ITEMS) { // JMenu nextMenu = new JMenu("More"); // currentMenu.add(nextMenu); // currentMenu = nextMenu; // numItems = 1; // } currentMenu.add(mi); } } // JMenuItem select = new JMenuItem("Select " + table.getName()); // select.addActionListener(new ActionListener () { // public void actionPerformed(ActionEvent e) { // modelEditor.select(table); // } // }); JMenuItem selectAsRoot = new JMenuItem("Focus " + table.getName()); selectAsRoot.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { GraphicalDataModelView.this.modelEditor.captureLayout(); try { modelEditor.setRootSelection(table); } finally { GraphicalDataModelView.this.modelEditor.checkLayoutStack(); } } }); JMenuItem dataBrowser = new JMenuItem("Browse Data"); dataBrowser.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { modelEditor.extractionModelFrame.openDataBrowser(table, ""); } }); JMenuItem zoomToFit = new JMenuItem("Zoom To Fit"); zoomToFit.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { zoomToFit(); } }); JMenuItem hide = new JMenuItem("Hide " + table.getName()); hide.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { GraphicalDataModelView.this.modelEditor.captureLayout(); hideTable(table); display.invalidate(); GraphicalDataModelView.this.modelEditor.checkLayoutStack(); } }); if (table.equals(root)) { hide.setEnabled(false); } JMenuItem toggleDetails = new JMenuItem(showDetails(table)? "Hide Details" : "Show Details"); toggleDetails.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { if (reversedShowDetailsTables.contains(table)) { reversedShowDetailsTables.remove(table); } else { reversedShowDetailsTables.add(table); } visualization.invalidateAll(); display.invalidate(); } }); JMenuItem mapColumns = new JMenuItem("XML Column Mapping"); mapColumns.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { modelEditor.openColumnMapper(table); } }); mapColumns.setEnabled(ScriptFormat.XML.equals(modelEditor.scriptFormat)); JMenuItem restrictAll = new JMenuItem("Disable Associations"); restrictAll.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(modelEditor.extractionModelFrame, "Disable each association with '" + model.getDisplayName(table) + "'?\n(except dependencies)?", "Add restrictions", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE)) { modelEditor.ignoreAll(table); } } }); restrictAll.setEnabled(modelEditor.isIgnoreAllApplicable(table)); JMenuItem removeRestrictions = new JMenuItem("Remove Restrictions"); removeRestrictions.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(modelEditor.extractionModelFrame, "Remove all restrictions from associations with '" + model.getDisplayName(table) + "'?", "Remove restrictions", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE)) { modelEditor.removeAllRestrictions(table); } } }); JMenuItem htmlRender = new JMenuItem("Open HTML Render"); htmlRender.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { modelEditor.extractionModelFrame.openHTMLRender(table); } }); JMenuItem queryBuilder = new JMenuItem("Query Builder"); queryBuilder.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { openQueryBuilder(table, false); } }); // JMenuItem shortestPath = new JMenuItem("Show shortest path"); // shortestPath.addActionListener(new ActionListener () { // public void actionPerformed(ActionEvent e) { // modelEditor.extractionModelFrame.showShortestPath(modelEditor.getSubject(), table); // } // }); removeRestrictions.setEnabled(modelEditor.isRemovalOfAllRestrictionsApplicable(table)); // JMenuItem findTable = new JMenuItem("Browse Closure"); // findTable.addActionListener(new ActionListener () { // public void actionPerformed(ActionEvent e) { // modelEditor.extractionModelFrame.openClosureView(table); // } // }); JMenuItem filterEditor= new JMenuItem("Edit Filters..."); filterEditor.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { modelEditor.extractionModelFrame.openFilterEditor(table); } }); popup.add(toggleDetails); popup.add(new JSeparator()); popup.add(hide); popup.add(selectAsRoot); if (navigateTo != null) { popup.add(navigateTo); } popup.add(dataBrowser); // popup.add(findTable); // popup.add(select); popup.add(new JSeparator()); popup.add(restrictAll); popup.add(removeRestrictions); popup.add(filterEditor); popup.add(mapColumns); popup.add(new JSeparator()); // popup.add(shortestPath); popup.add(zoomToFit); popup.add(new JSeparator()); popup.add(queryBuilder); popup.add(htmlRender); popup.add(new JSeparator()); JMenu insertModeMenu = new JMenu("Export Mode"); popup.add(insertModeMenu); JRadioButtonMenuItem insert = new JRadioButtonMenuItem("Insert"); insert.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { if (!Boolean.FALSE.equals(table.upsert)) { table.upsert = false; visualization.invalidateAll(); display.invalidate(); modelEditor.markDirty(); } } }); insertModeMenu.add(insert); JRadioButtonMenuItem upsert = new JRadioButtonMenuItem("Upsert/Merge"); insertModeMenu.add(upsert); upsert.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { if (!Boolean.TRUE.equals(table.upsert)) { table.upsert = true; visualization.invalidateAll(); display.invalidate(); modelEditor.markDirty(); } } }); JRadioButtonMenuItem deflt = new JRadioButtonMenuItem("Data model default (" + ((table.defaultUpsert? "Upsert" : "Insert") + ")")); insertModeMenu.add(deflt); deflt.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { if (table.upsert != null) { table.upsert = null; visualization.invalidateAll(); display.invalidate(); modelEditor.markDirty(); } } }); ButtonGroup bt = new ButtonGroup(); bt.add(insert); bt.add(upsert); bt.add(deflt); if (table.upsert == null) { deflt.setSelected(true); } if (Boolean.TRUE.equals(table.upsert)) { upsert.setSelected(true); } if (Boolean.FALSE.equals(table.upsert)) { insert.setSelected(true); } JMenu excludeMenu = new JMenu("Exclude from Deletion"); popup.add(excludeMenu); JRadioButtonMenuItem yes = new JRadioButtonMenuItem("Yes"); yes.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { if (!Boolean.TRUE.equals(table.excludeFromDeletion)) { table.excludeFromDeletion = true; visualization.invalidateAll(); display.invalidate(); modelEditor.markDirty(); } } }); excludeMenu.add(yes); JRadioButtonMenuItem no = new JRadioButtonMenuItem("No"); excludeMenu.add(no); no.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { if (!Boolean.FALSE.equals(table.excludeFromDeletion)) { table.excludeFromDeletion = false; visualization.invalidateAll(); display.invalidate(); modelEditor.markDirty(); } } }); deflt = new JRadioButtonMenuItem("Data model default (" + ((table.defaultExcludeFromDeletion? "Yes" : "No") + ")")); excludeMenu.add(deflt); deflt.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { if (table.excludeFromDeletion != null) { table.excludeFromDeletion = null; visualization.invalidateAll(); display.invalidate(); modelEditor.markDirty(); } } }); bt = new ButtonGroup(); bt.add(yes); bt.add(no); bt.add(deflt); if (table.excludeFromDeletion == null) { deflt.setSelected(true); } if (Boolean.TRUE.equals(table.excludeFromDeletion)) { yes.setSelected(true); } if (Boolean.FALSE.equals(table.excludeFromDeletion)) { no.setSelected(true); } return popup; } /** * Creates popup menu. * * @param association the association for which the menu pops up * @return the popup menu */ public JPopupMenu createPopupMenu(final Association association) { JPopupMenu popup = new JPopupMenu(); JMenuItem disable = new JMenuItem("Disable Association"); disable.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { setRestriction(association, true); } }); JMenuItem enable = new JMenuItem("Enable Association"); enable.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { setRestriction(association, false); } }); JMenuItem zoomToFit = new JMenuItem("Zoom To Fit"); zoomToFit.addActionListener(new ActionListener () { public void actionPerformed(ActionEvent e) { zoomToFit(); } }); disable.setEnabled(!association.isIgnored()); enable.setEnabled(association.isRestricted()); popup.add(disable); popup.add(enable); popup.add(new JSeparator()); popup.add(zoomToFit); return popup; } /** * Hides a table. * * @param table the table to hide */ protected void hideTable(Table table) { synchronized (visualization) { collapseTable(theGraph, table, true); } } /** * Closes the view. * @param storeLayout */ public void close(boolean storeLayout, boolean removeLayout) { if (storeLayout) { storeLayout(); } else if (removeLayout) { if (root != null) { LayoutStorage.removeAll(root.getName()); } } visualization.reset(); layout.cancel(); } /** * Zooms to fit. */ public void zoomToFit() { zoomToFitControl.zoomToFit(); } /** * Sets visual graph. * * @param g the (non-visual) model graph */ private void setGraph(Graph g) { // update graph inInitialization = true; visualization.removeGroup(graph); visualGraph = visualization.addGraph(graph, g); if (visualGraph.getNodeCount() > 1) { VisualItem f = (VisualItem) visualGraph.getNode(1); visualization.getGroup(Visualization.FOCUS_ITEMS).setTuple(f); f.setFixed(true); ((VisualItem) visualGraph.getNode(0)).setFixed(true); } inInitialization = false; } /** * The model as graph. */ private Graph theGraph; /** * Maps tables to their graph nodes. */ private Map<net.sf.jailer.datamodel.Table, Node> tableNodes = new HashMap<net.sf.jailer.datamodel.Table, Node>(); /** * Set of all tables which are currently expanded. */ Set<net.sf.jailer.datamodel.Table> expandedTables = new HashSet<net.sf.jailer.datamodel.Table>(); /** * Maps associations to their edges. */ private Map<Association, Edge> renderedAssociations = new HashMap<Association, Edge>(); private Map<Association, Node> renderedAssociationsAsNode = new HashMap<Association, Node>(); /** * The data model. */ private DataModel model = null; /** * Sets selected association. * * @param association the association to select or <code>null</code> to deselect */ public void setSelection(Association association) { synchronized (visualization) { if (selectedAssociation == null || association == null || !selectedAssociation.equals(association)) { if (selectedAssociation != null || association != null) { Association newlySelectedAssociation = null; if (association != null) { if (!(renderedAssociations.containsKey(association) || renderedAssociations.containsKey(association.reversalAssociation))) { newlySelectedAssociation = association; } } selectedAssociation = association; modelEditor.select(association); if (association != null) { expandTable(theGraph, association.source, association); expandTable(theGraph, association.destination, association); } tablesOnPath.clear(); associationsOnPath.clear(); if (selectedAssociation != null) { List<Association> path = getPathToRoot(selectedAssociation.destination, true, newlySelectedAssociation); if (path.isEmpty()) { path = getPathToRoot(selectedAssociation.destination, false, newlySelectedAssociation); } boolean highlightPath = true; if (path.isEmpty()) { highlightPath = false; path = modelEditor.getPathToRoot(selectedAssociation); } for (int i = 0; i < path.size(); ++i) { if (highlightPath) { associationsOnPath.add(path.get(i)); tablesOnPath.add(path.get(i).source.getName()); tablesOnPath.add(path.get(i).destination.getName()); } expandTable(theGraph, path.get(i).source, path.get(i)); expandTable(theGraph, path.get(i).destination, path.get(i)); } } invalidate(); } } } } /** * Gets shortest path from root to a given table. * * @param destination the table * @param ignoreInvisibleAssociations if <code>true</code>, find a path over visible associations only * @return shortest path from root to a given table */ private List<Association> getPathToRoot(Table destination, boolean ignoreInvisibleAssociations, Association newlySelectedAssociation) { List<Association> path = new ArrayList<Association>(); Map<Table, Table> successor = new HashMap<Table, Table>(); Map<Table, Association> outgoingAssociation = new HashMap<Table, Association>(); List<Table> agenda = new ArrayList<Table>(); agenda.add(destination); while (!agenda.isEmpty()) { Table table = agenda.remove(0); for (Association association: incomingAssociations(table, ignoreInvisibleAssociations, newlySelectedAssociation)) { if (!ignoreInvisibleAssociations || renderedAssociations.containsKey(association)|| renderedAssociations.containsKey(association.reversalAssociation)) { if (!successor.containsKey(association.source)) { successor.put(association.source, table); outgoingAssociation.put(association.source, association); agenda.add(association.source); if (association.source.equals(root)) { agenda.clear(); break; } } } } } if (successor.containsKey(root)) { for (Table table = root; !table.equals(destination); table = successor.get(table)) { Association association = outgoingAssociation.get(table); path.add(association); } } return path; } /** * Collects all non-disabled associations with a given table as destination. * * @param table the table */ private Collection<Association> incomingAssociations(Table table, boolean ignoreInvisibleAssociations, Association newlySelectedAssociation) { Collection<Association> result = new ArrayList<Association>(); for (Association association: table.associations) { if (association.reversalAssociation.getJoinCondition() != null || ((!ignoreInvisibleAssociations) && (association.reversalAssociation != newlySelectedAssociation)&& (renderedAssociations.containsKey(association) || renderedAssociations.containsKey(association.reversalAssociation)))) { result.add(association.reversalAssociation); } } return result; } /** * Gets model as graph. * * @param model the data model * @return graph */ private Graph getModelGraph(DataModel model, Set<Table> initiallyVisibleTables, boolean expandSubject) { tableNodes = new HashMap<net.sf.jailer.datamodel.Table, Node>(); expandedTables = new HashSet<net.sf.jailer.datamodel.Table>(); renderedAssociations = new HashMap<Association, Edge>(); Graph g = new Graph(true); Schema s = new Schema(); s.addColumn("label", String.class); s.addColumn("tooltip", String.class); s.addColumn("association", Association.class); s.addColumn("full", Boolean.class); g.addColumns(s); Node zoomBox = g.addNode(); zoomBox.setString("label", ZoomBoxControl.BOX_ITEM_LABEL); Table table = root; showTable(g, table); if (table != null) { if (!modelEditor.extractionModelFrame.showDisabledAssociations()) { initiallyVisibleTables.retainAll(table.closure(true)); } else { initiallyVisibleTables.retainAll(table.unrestrictedClosure(new HashSet<Table>())); } } for (Table t: initiallyVisibleTables) { showTable(g, t); } for (Table t: initiallyVisibleTables) { addEdges(g, t, null, new ArrayList<Table>(), true); } if (!initiallyVisibleTables.isEmpty()) { addEdges(g, table, null, new ArrayList<Table>(), true); } int nAssociatedTables = 0; if (table != null) { for (Association a: table.associations) { if (isVisualizable(a)) { ++nAssociatedTables; } } } if (initiallyVisibleTables.isEmpty() && nAssociatedTables <= 10 && expandSubject) { expandTable(g, table); } return g; } /** * Creates visible node for given table. * * @param graph the graph * @param table the table to show */ private boolean showTable(Graph graph, Table table) { if (table != null && !tableNodes.containsKey(table)) { Node n = graph.addNode(); n.setString("label", table.getName()); String tooltip = tableRenderer.getToolTip(table); n.setString("tooltip", tooltip); tableNodes.put(table, n); return true; } return false; } /** * Collapses a node representing a table. * * @param g the graph * @param table the table node * @param hideTable if <code>true</code>, hide table too */ private void collapseTable(Graph g, Table table, boolean hideTable) { if (table == null || (expandedTables.contains(table) || hideTable)) { Set<Association> associationsToKeep = new HashSet<Association>(); Set<Table> tablesToKeep = new HashSet<Table>(); collect(root, table, associationsToKeep, tablesToKeep); if (hideTable && table != null) { for (Association a: table.associations) { associationsToKeep.remove(a); associationsToKeep.remove(a.reversalAssociation); } } for (Table t: model.getTables()) { for (Association a: t.associations) { if (!associationsToKeep.contains(a)) { Edge e = renderedAssociations.get(a); if (e != null) { g.removeEdge(e); } renderedAssociations.remove(a); } } } for (Table t: model.getTables()) { for (Association a: t.associations) { if (!associationsToKeep.contains(a)) { Node e = renderedAssociationsAsNode.get(a); if (e != null) { g.removeNode(e); } renderedAssociationsAsNode.remove(a); } } } for (Table t: model.getTables()) { if ((t != table || hideTable) && !tablesToKeep.contains(t)) { Node n = tableNodes.get(t); if (n != null) { g.removeNode(n); } tableNodes.remove(t); for (Association a: t.associations) { expandedTables.remove(a.source); expandedTables.remove(a.destination); } } } if (table != null) { expandedTables.remove(table); if (!hideTable) { tablesToKeep.add(table); } } checkForExpansion(theGraph, tablesToKeep); } } /** * Collect all tables and associations in closure of root. * * @param root the root * @param ignore table to ignore * @param associationsToKeep to collect associations * @param tablesToKeep to collect tables */ private void collect(Table root, Table ignore, Set<Association> associationsToKeep, Set<Table> tablesToKeep) { if (root != ignore && !tablesToKeep.contains(root)) { tablesToKeep.add(root); for (Association a: root.associations) { if (isVisualizable(a) && (renderedAssociations.containsKey(a) || renderedAssociations.containsKey(a.reversalAssociation))) { associationsToKeep.add(a); associationsToKeep.add(a.reversalAssociation); collect(a.destination, ignore, associationsToKeep, tablesToKeep); collect(a.source, ignore, associationsToKeep, tablesToKeep); } } } } /** * Expands a node representing a table. * * @param g the graph * @param table the table node * * @return list of newly rendered tables */ private List<Table> expandTable(Graph g, net.sf.jailer.datamodel.Table table) { return expandTable(g, table, null); } /** * Expands a node representing a table. * * @param g the graph * @param table the table node * @param toRender if not null, the only association to make visible * * @return list of newly rendered tables */ private List<Table> expandTable(Graph g, net.sf.jailer.datamodel.Table table, Association toRender) { List<Table> result = new ArrayList<Table>(); if (table != null && (!expandedTables.contains(table) || toRender != null)) { List<Table> toCheck = new ArrayList<Table>(); result = addEdges(g, table, toRender, toCheck, false); // expandedTables.add(table); checkForExpansion(g, toCheck); } return result; } /** * Checks whether some tables are still expanded. * * @param g the graph * @param toCheck set of tables to check */ private void checkForExpansion(Graph g, java.util.Collection<Table> toCheck) { for (Table t: toCheck) { if (!expandedTables.contains(t)) { addEdges(g, t, null, new ArrayList<Table>(), true); boolean isExpanded = true; for (Association a: t.associations) { if (!isVisualizable(a)) { continue; } if (a.source != t && !tableNodes.containsKey(a.source) && !toCheck.contains(a.source)) { isExpanded = false; break; } if (a.destination != t && !tableNodes.containsKey(a.destination) && !toCheck.contains(a.destination)) { isExpanded = false; break; } } if (isExpanded) { expandedTables.add(t); } } } } /** * Checks whether some tables are still collapsed. * * @param g the graph * @param toCheck set of tables to check */ private void checkForCollapsed(Graph g, java.util.Collection<Table> toCheck) { for (Table t: toCheck) { if (expandedTables.contains(t)) { boolean isExpanded = true; for (Association a: t.associations) { if (!isVisualizable(a)) { continue; } if (!renderedAssociations.containsKey(a) && !renderedAssociations.containsKey(a.reversalAssociation)) { isExpanded = false; break; } } if (!isExpanded) { expandedTables.remove(t); } } } } /** * Adds edges for all associations of a table. * * @param table the table * @param toRender is not null, the only association to make visible * * @return list of newly rendered tables */ private List<Table> addEdges(Graph g, Table table, Association toRender, List<Table> toCheck, boolean visibleDestinationRequired) { List<Table> result = new ArrayList<Table>(); toCheck.add(table); for (Association a: table.associations) { if (toRender != null && toRender != a) { continue; } if (!isVisualizable(a) && (toRender == null || a != toRender)) { continue; } if (visibleDestinationRequired && !tableNodes.containsKey(a.destination)) { continue; } if (!renderedAssociations.containsKey(a) && !renderedAssociations.containsKey(a.reversalAssociation)) { toCheck.add(a.destination); toCheck.add(a.source); if (showTable(g, a.source)) { result.add(a.source); } if (showTable(g, a.destination)) { result.add(a.destination); } String tooltip = a.getJoinCondition(); if (!associationIsUnique(a)) { Node an = g.addNode(); an.set("association", a); an.setString("label", a.getName() + "#"); an.setString("tooltip", tooltip); renderedAssociationsAsNode.put(a, an); Edge ae = g.addEdge(an, tableNodes.get(a.source)); ae.set("association", a.reversalAssociation); ae.set("full", Boolean.FALSE); ae.setString("tooltip", tooltip); renderedAssociations.put(a.reversalAssociation, ae); Edge be = g.addEdge(an, tableNodes.get(a.destination)); be.set("association", a); be.set("full", Boolean.FALSE); be.setString("tooltip", tooltip); renderedAssociations.put(a, be); } else { Edge e = g.addEdge(tableNodes.get(a.source), tableNodes.get(a.destination)); e.set("association", a); e.set("full", Boolean.TRUE); e.setString("tooltip", tooltip); renderedAssociations.put(a, e); } } } return result; } /** * Searches another association with same destination. * * @param a an association * @return <code>true</code> if no other association is found */ private boolean associationIsUnique(Association a) { if (a.source == a.destination) { return false; } List<Association> all = new ArrayList<Association>(); all.addAll(a.source.associations); all.addAll(a.destination.associations); for (Association b: all) { if (a != b && a != b.reversalAssociation) { if (a.destination == b.destination && a.source == b.source) { return false; } if (a.destination == b.source && a.source == b.destination) { return false; } } } return true; } /** * Extention of {@link ZoomToFitControl}. */ private final class ZoomToFitControlExtension extends ZoomToFitControl { private final DataModel model; private final long duration; private ZoomToFitControlExtension(String group, int margin, long duration, int button, DataModel model) { super(group, margin, duration, button); this.duration = duration; this.model = model; } @Override public void itemClicked(VisualItem item, MouseEvent e) { // click on table opens pop-up menu if (model.getTable(item.getString("label")) != null || item.get("association") != null) { return; } super.itemClicked(item, e); } /** * Zooms to fit. */ public void zoomToFit() { Visualization vis = display.getVisualization(); Rectangle2D bounds = vis.getBounds(Visualization.ALL_ITEMS); GraphicsLib.expand(bounds, 50 + (int)(1/display.getScale())); DisplayLib.fitViewToBounds(display, bounds, duration); } } /** * Renderer for {@link Association}s. */ private final class CompositeAssociationRenderer implements Renderer { private AssociationRenderer associationRenderer = new AssociationRenderer(false); private AssociationRenderer reversedAssociationRenderer = new AssociationRenderer(true); private AssociationRenderer associationFullRenderer = new AssociationRenderer(); public boolean useAssociationRendererForLocation = false; public boolean locatePointWithAssociationRenderer(Point2D p, VisualItem item) { return associationRenderer.locatePoint(p, item); } public boolean locatePoint(Point2D p, VisualItem item) { if (useAssociationRendererForLocation) { return locatePointWithAssociationRenderer(p, item); } return associationFullRenderer.locatePoint(p, item); } public void render(Graphics2D g, VisualItem item) { item.setInteractive(true); boolean isSelected = selectedAssociation != null && selectedAssociation.equals(item.get("association")); boolean isReversedSelected = selectedAssociation != null && selectedAssociation.reversalAssociation.equals(item.get("association")); associationFullRenderer.render(g, item, isSelected); associationRenderer.render(g, item, isSelected); reversedAssociationRenderer.render(g, item, isReversedSelected); } public void setBounds(VisualItem item) { associationFullRenderer.setBounds(item); } } /** * Looks up "show disabled associations" setting and * decides whether an association is visualizable. * * @param association the association to check * @return true if association is visualizable */ private boolean isVisualizable(Association association) { return modelEditor.extractionModelFrame.showDisabledAssociations() || !association.isIgnored(); } /** * Expands all tables. * * @param reachableTable if not <code>null</code>, expand only tables from which this table is reachable */ public void expandAll(boolean expandOnlyVisibleTables, Table reachableTable) { modelEditor.captureLayout(); try { Set<Table> onPath = new HashSet<Table>(); if (reachableTable != null) { List<Table> toExpand = new ArrayList<Table>(); toExpand.addAll(tableNodes.keySet()); onPath.addAll(tableNodes.keySet()); while (!toExpand.isEmpty()) { Table table = toExpand.remove(0); for (Association association: table.associations) { if (!association.isIgnored() && !onPath.contains(association.destination)) { if (association.destination.closure(true).contains(reachableTable)) { onPath.add(association.destination); toExpand.add(association.destination); } } } } for (;;) { Set<Table> toRemove = new HashSet<Table>(); Set<Table> refTables = new HashSet<Table>(); Set<Table> toIgnore = new HashSet<Table>(); for (Table initTable: tableNodes.keySet()) { toIgnore.addAll(initTable.closure(true)); } toIgnore.removeAll(onPath); for (Table table: onPath) { /* if (!table.equals(reachableTable) && !tableNodes.containsKey(table)) */ { refTables.clear(); for (Association association: table.associations) { if (!association.destination.equals(table) && onPath.contains(association.destination)) { refTables.add(association.destination); } } boolean isIn = toIgnore.contains(table); toIgnore.add(table); for (Table toCheck: refTables) { if (!toCheck.equals(reachableTable) && !tableNodes.containsKey(toCheck)) { boolean reach = toCheck.closure(toIgnore, true).contains(reachableTable); if (!reach) { model.transpose(); for (Table initTable: tableNodes.keySet()) { reach = toCheck.closure(toIgnore, true).contains(initTable); if (reach) { break; } } model.transpose(); } if (!reach) { toRemove.add(toCheck); } } } if (!isIn) { toIgnore.remove(table); } } } if (!toRemove.isEmpty()) { onPath.removeAll(toRemove); toIgnore.addAll(toRemove); toRemove.clear(); } else { break; } } } boolean stop = false; List<Table> toExpand = new ArrayList<Table>(); toExpand.addAll(tableNodes.keySet()); while (!stop) { boolean askNow = false; synchronized (visualization) { boolean ask = tableNodes.size() <= EXPAND_LIMIT; while (!toExpand.isEmpty()) { Table table = toExpand.remove(0); if (reachableTable != null) { for (Association association: table.associations) { if (onPath.contains(association.destination) && !tableNodes.containsKey(association.destination)) { List<Table> tables = expandTable(theGraph, table, association); if (!expandOnlyVisibleTables) { toExpand.addAll(tables); } } } // for (Association association: table.associations) { // if (!association.isIgnored() && !tableNodes.containsKey(association.destination)) { // if (association.destination.closure(true).contains(reachableTable)) { // List<Table> tables = expandTable(theGraph, table, association); // if (!expandOnlyVisibleTables) { // toExpand.addAll(tables); // } // } // } // } } else { List<Table> tables = expandTable(theGraph, table); if (!expandOnlyVisibleTables) { toExpand.addAll(tables); } } if (ask && tableNodes.size() > EXPAND_LIMIT) { askNow = true; break; } } } if (askNow) { int option = JOptionPane.showConfirmDialog(modelEditor.extractionModelFrame, "More than " + EXPAND_LIMIT + " visible tables!\nStop expansion?", "", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE); if (JOptionPane.NO_OPTION != option) { stop = true; if (JOptionPane.CANCEL_OPTION == option) { GraphicalDataModelView.this.modelEditor.undo(); } } } else { stop = true; } } } finally { GraphicalDataModelView.this.modelEditor.checkLayoutStack(); } } /** * Resets expanded/collapsed status of each visible table. */ public void resetExpandedState() { synchronized (visualization) { hideTable(null); checkForCollapsed(theGraph, tableNodes.keySet()); checkForExpansion(theGraph, tableNodes.keySet()); visualization.invalidateAll(); } } /** * Sets fix property of all visual nodes. * * @param fix the property value */ public void setFix(boolean fix) { synchronized (visualization) { for (int i = visualGraph.getNodeCount() - 1; i >= 0; --i) { VisualItem n = (VisualItem) visualGraph.getNode(i); n.setFixed(fix); } } } private Set<Table> reversedShowDetailsTables = new HashSet<Table>(); /** * Decides whether to show details of a table. * * @param table the table * @return <code>true</code> iff details are shown */ public boolean showDetails(Table table) { if (reversedShowDetailsTables.contains(table)) { return !showTableDetails; } return showTableDetails; } /** * Updates table details mode. */ public void updateTableDetailsMode() { showTableDetails = modelEditor.extractionModelFrame.showTableDetails(); // dragForce.setParameter(DragForce.DRAG_COEFF, showTableDetails? 0.05f : 0.1f); reversedShowDetailsTables.clear(); visualization.invalidateAll(); visualization.repaint(); } /** * Gets all visible tables. * * @return set of all tables which are currently visible */ public Set<Table> getVisibleTables() { return tableNodes.keySet(); } private static DisplayExporter displayExporter = new DisplayExporter(); public boolean inImageExport = false; public void exportDisplayToImage() throws Exception { Association oldAssociation = selectedAssociation; Set<String> oldTablesOnPath = tablesOnPath; try { synchronized (this) { selectedAssociation = null; tablesOnPath = new HashSet<String>(); inImageExport = true; } displayExporter.export(display); } finally { synchronized (this) { inImageExport = false; selectedAssociation = oldAssociation; tablesOnPath = oldTablesOnPath; } } } /** * Opens query builder dialog. * * @param table subject of query * @param usePath if <code>true</code>, immediately build query based on selected path */ public void openQueryBuilder(Table table, boolean usePath) { new QueryBuilderDialog(this.modelEditor.extractionModelFrame).buildQuery(table, usePath, true, associationsOnPath, null, model); } /** * Gets visibility of a table. * * @param table the table * @return <code>true</code> iff table is visible */ public boolean isTableVisible(Table table) { return tableNodes.containsKey(table); } /** * Selects a table. * * @param table the table */ public void selectTable(Table table) { Association toS = null; for (Association a: table.associations) { if (renderedAssociations.containsKey(a) || renderedAssociations.containsKey(a.reversalAssociation)) { toS = a.reversalAssociation; break; } } if (toS == null) { GraphicalDataModelView.this.modelEditor.select(table); } else { GraphicalDataModelView.this.modelEditor.select(toS); } Association sa = selectedAssociation; setSelection(null); setSelection(sa); } public void setRestriction(final Association association, boolean ignore) { setSelection(association); modelEditor.restrictionEditor.ignore.setSelected(ignore); modelEditor.onApply(false); } public Set<String> visibleItems() { Set<String> result = new HashSet<String>(); synchronized (visualization) { Iterator items = visualization.items(BooleanLiteral.TRUE); while (items.hasNext()) { VisualItem item = (VisualItem)items.next(); if (item.canGetString("label") ) { String tableName; tableName = item.getString("label"); if (tableName != null) { result.add(tableName); } } } } return result; } private static final long serialVersionUID = -5938101712807557555L; }