From 74fcac338eef607efce8308f96dd0771ea503093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 4 Feb 2018 12:22:02 +0100 Subject: [PATCH] Copy GV source to clipboard in rightclick menu of Graph. --- .../decompiler/flash/gui/GraphDialog.java | 367 ++++++------------ .../flash/gui/action/ActionPanel.java | 26 -- .../flash/gui/graph/AbstractGraphPanel.java | 34 ++ .../flash/gui/graph/GraphPanelSimple.java | 266 +++++++++++++ .../flash/gui/graph/GraphVizDotCommands.java | 84 ++++ .../flash/gui/graph/GraphVizGraphPanel.java | 81 ++++ .../flash/gui/locales/GraphDialog.properties | 2 + .../flash/gui/locales/MainFrame.properties | 3 - 8 files changed, 592 insertions(+), 271 deletions(-) create mode 100644 src/com/jpexs/decompiler/flash/gui/graph/AbstractGraphPanel.java create mode 100644 src/com/jpexs/decompiler/flash/gui/graph/GraphPanelSimple.java create mode 100644 src/com/jpexs/decompiler/flash/gui/graph/GraphVizDotCommands.java create mode 100644 src/com/jpexs/decompiler/flash/gui/graph/GraphVizGraphPanel.java diff --git a/src/com/jpexs/decompiler/flash/gui/GraphDialog.java b/src/com/jpexs/decompiler/flash/gui/GraphDialog.java index 525fadf74..386dfb59d 100644 --- a/src/com/jpexs/decompiler/flash/gui/GraphDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/GraphDialog.java @@ -16,24 +16,40 @@ */ package com.jpexs.decompiler.flash.gui; +import com.jpexs.decompiler.flash.exporters.script.PcodeGraphVizExporter; +import com.jpexs.decompiler.flash.gui.AppDialog; +import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.gui.graph.AbstractGraphPanel; +import com.jpexs.decompiler.flash.gui.graph.GraphPanelSimple; +import com.jpexs.decompiler.flash.gui.graph.GraphVizGraphPanel; +import com.jpexs.decompiler.flash.helpers.StringBuilderTextWriter; import com.jpexs.decompiler.graph.Graph; -import com.jpexs.decompiler.graph.GraphPart; import java.awt.BorderLayout; -import java.awt.Color; import java.awt.Container; +import java.awt.Cursor; import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Graphics2D; +import java.awt.FlowLayout; +import java.awt.Font; import java.awt.Point; -import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.Toolkit; import java.awt.Window; -import java.awt.geom.AffineTransform; -import java.awt.geom.Line2D; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JLabel; +import javax.swing.JMenuItem; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JScrollPane; +import javax.swing.JViewport; +import javax.swing.SwingUtilities; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; /** * @@ -41,217 +57,7 @@ import javax.swing.JScrollPane; */ public class GraphDialog extends AppDialog { - private class GraphPanel extends JPanel { - - private static final int SPACE_VERTICAL = 16; - - private static final int SPACE_HORIZONTAL = 10; - - private static final int SPACE_BACKLINKS = 5; - - private static final int BLOCK_WIDTH = 200; - - private static final int BLOCK_HEIGHT = 20; - - private final HashMap partPos = new HashMap<>(); - - private final Point size; - - private int backLinksLeft = 0; - - private int backLinksRight = 0; - - private final GraphPart head; - - public GraphPanel(Graph graph) throws InterruptedException { - graph.init(null); - size = getPartPositions(head = graph.heads.get(0), SPACE_VERTICAL + SPACE_VERTICAL + BLOCK_HEIGHT / 2, getPartWidth(graph.heads.get(0), new HashSet<>()) * (BLOCK_WIDTH + SPACE_HORIZONTAL) / 2 - SPACE_HORIZONTAL, partPos, true); - backLinksLeft = 1; - backLinksRight = 1; - for (GraphPart part : partPos.keySet()) { - Point p = partPos.get(part); - for (GraphPart n : part.nextParts) { - Point npos = partPos.get(n); - if (npos.y < p.y) { - if (p.x > size.x / 2) { - backLinksRight++; - } else { - backLinksLeft++; - } - } - } - } - size.x += 2 * SPACE_BACKLINKS + backLinksLeft * SPACE_BACKLINKS + backLinksRight * SPACE_BACKLINKS; - setPreferredSize(new Dimension(size.x, size.y)); - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - Graphics2D g2 = (Graphics2D) g; - /*g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON);*/ - g2.setColor(Color.black); - int startX = SPACE_BACKLINKS + backLinksLeft * SPACE_BACKLINKS; - int blLeft = 0; - int blRight = 0; - for (GraphPart part : partPos.keySet()) { - Point p = partPos.get(part); - g2.setColor(Color.white); - g2.fillRect(startX + p.x - BLOCK_WIDTH / 2, p.y - BLOCK_HEIGHT / 2, BLOCK_WIDTH, BLOCK_HEIGHT); - g2.setColor(Color.black); - g2.drawRect(startX + p.x - BLOCK_WIDTH / 2, p.y - BLOCK_HEIGHT / 2, BLOCK_WIDTH, BLOCK_HEIGHT); - g2.drawString(part.toString(), startX + p.x - g2.getFontMetrics().stringWidth(part.toString()) / 2, p.y + g2.getFontMetrics().getHeight() / 2); - } - - Point hp = partPos.get(head); - drawArrow(g2, startX + hp.x, hp.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL, startX + hp.x, hp.y - BLOCK_HEIGHT / 2); - - for (GraphPart part : partPos.keySet()) { - Point p = partPos.get(part); - - for (int n = 0; n < part.nextParts.size(); n++) { - boolean isIf = part.nextParts.size() == 2; - if (isIf) { - if (n == 0) { - g2.setColor(new Color(0, 0x80, 0)); - } else { - g2.setColor(Color.red); - } - } else { - g2.setColor(Color.black); - } - Point npos = partPos.get(part.nextParts.get(n)); - if (npos.y < p.y) { - int sidex = startX; - if (p.x > size.x / 2) { - blRight++; - sidex = size.x - backLinksRight * SPACE_BACKLINKS; - sidex += SPACE_BACKLINKS + SPACE_BACKLINKS * blRight; - } else { - blLeft++; - sidex -= SPACE_BACKLINKS + SPACE_BACKLINKS * blLeft; - } - g2.drawLine(startX + p.x, p.y + BLOCK_HEIGHT / 2, startX + p.x, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); - g2.drawLine(startX + p.x, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2, sidex, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); - g2.drawLine(sidex, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, sidex, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); - drawArrow(g2, sidex, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2); - g2.drawLine(startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2); - } else { - drawArrow(g2, startX + p.x, p.y + BLOCK_HEIGHT / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2); - g2.drawLine(startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2); - } - } - } - } - - private Point getPartSubPositions(Point ret, int totalWidth, GraphPart part, int y, int x, HashMap used) { - if (used.containsKey(part)) { - Point p = used.get(part); - return new Point(x, y); - } - used.put(part, new Point(x, y)); - if (part.nextParts.size() > 0) { - int cx = x - totalWidth / 2; - for (int p = 0; p < part.nextParts.size(); p++) { - HashSet k = new HashSet<>(); - k.addAll(used.keySet()); - int partWidth = getPartWidth(part.nextParts.get(p), k); - int cellWidth = partWidth * (BLOCK_WIDTH + SPACE_HORIZONTAL); - Point pt = getPartPositions(part.nextParts.get(p), y + BLOCK_HEIGHT + SPACE_VERTICAL, cx + cellWidth / 2, used, false); - if (pt.x > ret.x) { - ret.x = pt.x; - } - if (pt.y > ret.y) { - ret.y = pt.y; - } - cx += cellWidth; - - } - cx = x - totalWidth / 2; - for (int p = 0; p < part.nextParts.size(); p++) { - HashSet k = new HashSet<>(); - k.addAll(used.keySet()); - int cellWidth = getPartWidth(part.nextParts.get(p), k) * (BLOCK_WIDTH + SPACE_HORIZONTAL); - - HashSet hs = new HashSet<>(); - hs.addAll(used.keySet()); - int totalWidthParts2 = getPartWidth(part.nextParts.get(p), hs); - int totalWidth2 = totalWidthParts2 * (BLOCK_WIDTH + SPACE_HORIZONTAL); - - Point pt = getPartSubPositions(ret, totalWidth2, part.nextParts.get(p), y + BLOCK_HEIGHT + SPACE_VERTICAL, cx + cellWidth / 2, used); - if (pt.x > ret.x) { - ret.x = pt.x; - } - if (pt.y > ret.y) { - ret.y = pt.y; - } - cx += cellWidth; - } - - } - return ret; - } - - private Point getPartPositions(GraphPart part, int y, int x, HashMap used, boolean goSub) { - HashMap l = new HashMap<>(); - l.putAll(used); - HashSet hs = new HashSet<>(); - hs.addAll(l.keySet()); - int totalWidthParts = getPartWidth(part, hs); - int totalWidth = totalWidthParts * (BLOCK_WIDTH + SPACE_HORIZONTAL); - - if (used.containsKey(part)) { - Point p = used.get(part); - return new Point(x, y); - } - Point ret = new Point(x - BLOCK_WIDTH / 2 - SPACE_HORIZONTAL / 2 + BLOCK_WIDTH, y + BLOCK_HEIGHT); - if (goSub) { - Point p2 = getPartSubPositions(ret, totalWidth, part, y, x, used); - if (p2.x > ret.x) { - ret.x = p2.x; - } - if (p2.y > ret.y) { - ret.y = p2.y; - } - } - return ret; - } - - private int getPartHeight(GraphPart part, List used) { - if (used.contains(part)) { - return 1; - } - used.add(part); - int maxH = 0; - for (int p = 0; p < part.nextParts.size(); p++) { - int h = getPartHeight(part.nextParts.get(p), used); - if (h > maxH) { - maxH = h; - } - } - return 1 + maxH; - } - - private int getPartWidth(GraphPart part, HashSet used) { - - if (used.contains(part)) { - return 1; - } - used.add(part); - if (part.nextParts.isEmpty()) { - return 1; - } - int w = 0; - for (GraphPart subpart : part.nextParts) { - w += getPartWidth(subpart, used); - } - return w; - } - } - - GraphPanel gp; + AbstractGraphPanel gp; int scrollBarWidth; @@ -261,12 +67,57 @@ public class GraphDialog extends AppDialog { int frameHeightDiff; + private Graph graph; + + private static final Logger logger = Logger.getLogger(GraphDialog.class.getName()); + public GraphDialog(Window owner, Graph graph, String name) throws InterruptedException { super(owner); + this.graph = graph; setSize(500, 500); Container cnt = getContentPane(); cnt.setLayout(new BorderLayout()); - gp = new GraphPanel(graph); + if (GraphVizGraphPanel.isAvailable()) { + gp = new GraphVizGraphPanel(graph); + } else { + gp = new GraphPanelSimple(graph); + JPanel betterGraphInfo = new JPanel(new FlowLayout()); + JLabel lab = new JLabel("" + translate("graph.better.dot") + ""); + lab.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + lab.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + Main.advancedSettings("paths"); + } + + }); + betterGraphInfo.add(lab); + cnt.add(betterGraphInfo, BorderLayout.SOUTH); + } + + JPopupMenu popupMenu = new JPopupMenu(); + JMenuItem copyMenuItem = new JMenuItem(translate("menu.copygraph.gv")); + copyMenuItem.addActionListener(this::copyToClipBoardActionPerformed); + popupMenu.add(copyMenuItem); + gp.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + maybeShowPopup(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + maybeShowPopup(e); + } + + private void maybeShowPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + popupMenu.show(e.getComponent(), + e.getX(), e.getY()); + } + } + }); + setTitle(translate("graph") + " " + name); JScrollPane scrollPane = new JScrollPane(gp); scrollBarWidth = scrollPane.getVerticalScrollBar().getPreferredSize().width; @@ -282,6 +133,58 @@ public class GraphDialog extends AppDialog { View.setWindowIcon(this); + MouseAdapter ma = new MouseAdapter() { + + private Point origin; + + @Override + public void mousePressed(MouseEvent e) { + origin = new Point(e.getPoint()); + } + + @Override + public void mouseReleased(MouseEvent e) { + } + + @Override + public void mouseDragged(MouseEvent e) { + if (origin != null) { + JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, gp); + if (viewPort != null) { + int deltaX = origin.x - e.getX(); + int deltaY = origin.y - e.getY(); + + Rectangle view = viewPort.getViewRect(); + view.x += deltaX; + view.y += deltaY; + + gp.scrollRectToVisible(view); + } + } + } + + }; + + gp.addMouseListener(ma); + gp.addMouseMotionListener(ma); + } + + private void copyToClipBoardActionPerformed(ActionEvent evt) { + StringBuilder stringBuilder = new StringBuilder(); + try { + StringBuilderTextWriter stringBuilderWriter = new StringBuilderTextWriter(null, stringBuilder); + new PcodeGraphVizExporter().export(graph, stringBuilderWriter); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Error while generating graph", ex); + return; + } + try { + StringSelection stringSelection = new StringSelection(stringBuilder.toString()); + Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard(); + clpbrd.setContents(stringSelection, null); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Cannot copy to clipboard", ex); + } } @Override @@ -327,24 +230,4 @@ public class GraphDialog extends AppDialog { setSize(new Dimension(dim.width + frameWidthDiff, dim.height + frameHeightDiff)); } - private void drawArrow(Graphics g, int x1, int y1, int x2, int y2) { - Polygon arrowHead = new Polygon(); - arrowHead.addPoint(0, 0); - arrowHead.addPoint(-3, -8); - arrowHead.addPoint(3, -8); - Line2D.Double line = new Line2D.Double(x1, y1, x2, y2); - AffineTransform tx = new AffineTransform(); - tx.setToIdentity(); - double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1); - tx.translate(line.x2, line.y2); - tx.rotate((angle - Math.PI / 2d)); - - Graphics2D g2d = (Graphics2D) g; - AffineTransform oldTransform = g2d.getTransform(); - g2d.draw(line); - tx.preConcatenate(oldTransform); - g2d.setTransform(tx); - g2d.fill(arrowHead); - g2d.setTransform(oldTransform); - } } diff --git a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java index 9d4bce04e..b1edafe96 100644 --- a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java @@ -645,11 +645,6 @@ public class ActionPanel extends JPanel implements SearchListener. + */ +package com.jpexs.decompiler.flash.gui.graph; + +import com.jpexs.decompiler.graph.Graph; +import javax.swing.JPanel; + +/** + * + * @author JPEXS + */ +public abstract class AbstractGraphPanel extends JPanel { + + protected Graph graph; + + public AbstractGraphPanel(Graph graph) throws InterruptedException { + this.graph = graph; + graph.init(null); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/graph/GraphPanelSimple.java b/src/com/jpexs/decompiler/flash/gui/graph/GraphPanelSimple.java new file mode 100644 index 000000000..f217671c4 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/graph/GraphPanelSimple.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2018 Jindra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.graph; + +import com.jpexs.decompiler.graph.Graph; +import com.jpexs.decompiler.graph.GraphPart; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +/** + * + * @author Jindra + */ +public class GraphPanelSimple extends AbstractGraphPanel { + + private static final int SPACE_VERTICAL = 16; + + private static final int SPACE_HORIZONTAL = 10; + + private static final int SPACE_BACKLINKS = 5; + + private static final int BLOCK_WIDTH = 200; + + private static final int BLOCK_HEIGHT = 20; + + private final HashMap partPos = new HashMap<>(); + + private final Point size; + + private int backLinksLeft = 0; + + private int backLinksRight = 0; + + private final GraphPart head; + + public GraphPanelSimple(Graph graph) throws InterruptedException { + super(graph); + size = getPartPositions(head = graph.heads.get(0), SPACE_VERTICAL + SPACE_VERTICAL + BLOCK_HEIGHT / 2, getPartWidth(graph.heads.get(0), new HashSet<>()) * (BLOCK_WIDTH + SPACE_HORIZONTAL) / 2 - SPACE_HORIZONTAL, partPos, true); + backLinksLeft = 1; + backLinksRight = 1; + for (GraphPart part : partPos.keySet()) { + Point p = partPos.get(part); + for (GraphPart n : part.nextParts) { + Point npos = partPos.get(n); + if (npos.y < p.y) { + if (p.x > size.x / 2) { + backLinksRight++; + } else { + backLinksLeft++; + } + } + } + } + size.x += 2 * SPACE_BACKLINKS + backLinksLeft * SPACE_BACKLINKS + backLinksRight * SPACE_BACKLINKS; + setPreferredSize(new Dimension(size.x, size.y)); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + Graphics2D g2 = (Graphics2D) g; + /*g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON);*/ + g2.setColor(Color.black); + int startX = SPACE_BACKLINKS + backLinksLeft * SPACE_BACKLINKS; + int blLeft = 0; + int blRight = 0; + for (GraphPart part : partPos.keySet()) { + Point p = partPos.get(part); + g2.setColor(Color.white); + g2.fillRect(startX + p.x - BLOCK_WIDTH / 2, p.y - BLOCK_HEIGHT / 2, BLOCK_WIDTH, BLOCK_HEIGHT); + g2.setColor(Color.black); + g2.drawRect(startX + p.x - BLOCK_WIDTH / 2, p.y - BLOCK_HEIGHT / 2, BLOCK_WIDTH, BLOCK_HEIGHT); + g2.drawString(part.toString(), startX + p.x - g2.getFontMetrics().stringWidth(part.toString()) / 2, p.y + g2.getFontMetrics().getHeight() / 2); + } + + Point hp = partPos.get(head); + drawArrow(g2, startX + hp.x, hp.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL, startX + hp.x, hp.y - BLOCK_HEIGHT / 2); + + for (GraphPart part : partPos.keySet()) { + Point p = partPos.get(part); + + for (int n = 0; n < part.nextParts.size(); n++) { + boolean isIf = part.nextParts.size() == 2; + if (isIf) { + if (n == 0) { + g2.setColor(new Color(0, 0x80, 0)); + } else { + g2.setColor(Color.red); + } + } else { + g2.setColor(Color.black); + } + Point npos = partPos.get(part.nextParts.get(n)); + if (npos.y < p.y) { + int sidex = startX; + if (p.x > size.x / 2) { + blRight++; + sidex = size.x - backLinksRight * SPACE_BACKLINKS; + sidex += SPACE_BACKLINKS + SPACE_BACKLINKS * blRight; + } else { + blLeft++; + sidex -= SPACE_BACKLINKS + SPACE_BACKLINKS * blLeft; + } + g2.drawLine(startX + p.x, p.y + BLOCK_HEIGHT / 2, startX + p.x, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); + g2.drawLine(startX + p.x, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2, sidex, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); + g2.drawLine(sidex, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, sidex, p.y + BLOCK_HEIGHT / 2 + SPACE_VERTICAL / 2); + drawArrow(g2, sidex, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2); + g2.drawLine(startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2); + } else { + drawArrow(g2, startX + p.x, p.y + BLOCK_HEIGHT / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2); + g2.drawLine(startX + npos.x, npos.y - BLOCK_HEIGHT / 2 - SPACE_VERTICAL / 2, startX + npos.x, npos.y - BLOCK_HEIGHT / 2); + } + } + } + } + + private Point getPartSubPositions(Point ret, int totalWidth, GraphPart part, int y, int x, HashMap used) { + if (used.containsKey(part)) { + Point p = used.get(part); + return new Point(x, y); + } + used.put(part, new Point(x, y)); + if (part.nextParts.size() > 0) { + int cx = x - totalWidth / 2; + for (int p = 0; p < part.nextParts.size(); p++) { + HashSet k = new HashSet<>(); + k.addAll(used.keySet()); + int partWidth = getPartWidth(part.nextParts.get(p), k); + int cellWidth = partWidth * (BLOCK_WIDTH + SPACE_HORIZONTAL); + Point pt = getPartPositions(part.nextParts.get(p), y + BLOCK_HEIGHT + SPACE_VERTICAL, cx + cellWidth / 2, used, false); + if (pt.x > ret.x) { + ret.x = pt.x; + } + if (pt.y > ret.y) { + ret.y = pt.y; + } + cx += cellWidth; + + } + cx = x - totalWidth / 2; + for (int p = 0; p < part.nextParts.size(); p++) { + HashSet k = new HashSet<>(); + k.addAll(used.keySet()); + int cellWidth = getPartWidth(part.nextParts.get(p), k) * (BLOCK_WIDTH + SPACE_HORIZONTAL); + + HashSet hs = new HashSet<>(); + hs.addAll(used.keySet()); + int totalWidthParts2 = getPartWidth(part.nextParts.get(p), hs); + int totalWidth2 = totalWidthParts2 * (BLOCK_WIDTH + SPACE_HORIZONTAL); + + Point pt = getPartSubPositions(ret, totalWidth2, part.nextParts.get(p), y + BLOCK_HEIGHT + SPACE_VERTICAL, cx + cellWidth / 2, used); + if (pt.x > ret.x) { + ret.x = pt.x; + } + if (pt.y > ret.y) { + ret.y = pt.y; + } + cx += cellWidth; + } + + } + return ret; + } + + private Point getPartPositions(GraphPart part, int y, int x, HashMap used, boolean goSub) { + HashMap l = new HashMap<>(); + l.putAll(used); + HashSet hs = new HashSet<>(); + hs.addAll(l.keySet()); + int totalWidthParts = getPartWidth(part, hs); + int totalWidth = totalWidthParts * (BLOCK_WIDTH + SPACE_HORIZONTAL); + + if (used.containsKey(part)) { + Point p = used.get(part); + return new Point(x, y); + } + Point ret = new Point(x - BLOCK_WIDTH / 2 - SPACE_HORIZONTAL / 2 + BLOCK_WIDTH, y + BLOCK_HEIGHT); + if (goSub) { + Point p2 = getPartSubPositions(ret, totalWidth, part, y, x, used); + if (p2.x > ret.x) { + ret.x = p2.x; + } + if (p2.y > ret.y) { + ret.y = p2.y; + } + } + return ret; + } + + private int getPartHeight(GraphPart part, List used) { + if (used.contains(part)) { + return 1; + } + used.add(part); + int maxH = 0; + for (int p = 0; p < part.nextParts.size(); p++) { + int h = getPartHeight(part.nextParts.get(p), used); + if (h > maxH) { + maxH = h; + } + } + return 1 + maxH; + } + + private int getPartWidth(GraphPart part, HashSet used) { + + if (used.contains(part)) { + return 1; + } + used.add(part); + if (part.nextParts.isEmpty()) { + return 1; + } + int w = 0; + for (GraphPart subpart : part.nextParts) { + w += getPartWidth(subpart, used); + } + return w; + } + + private void drawArrow(Graphics g, int x1, int y1, int x2, int y2) { + Polygon arrowHead = new Polygon(); + arrowHead.addPoint(0, 0); + arrowHead.addPoint(-3, -8); + arrowHead.addPoint(3, -8); + Line2D.Double line = new Line2D.Double(x1, y1, x2, y2); + AffineTransform tx = new AffineTransform(); + tx.setToIdentity(); + double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1); + tx.translate(line.x2, line.y2); + tx.rotate((angle - Math.PI / 2d)); + + Graphics2D g2d = (Graphics2D) g; + AffineTransform oldTransform = g2d.getTransform(); + g2d.draw(line); + tx.preConcatenate(oldTransform); + g2d.setTransform(tx); + g2d.fill(arrowHead); + g2d.setTransform(oldTransform); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/graph/GraphVizDotCommands.java b/src/com/jpexs/decompiler/flash/gui/graph/GraphVizDotCommands.java new file mode 100644 index 000000000..50889984e --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/graph/GraphVizDotCommands.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 Jindra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.graph; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import javax.imageio.ImageIO; + +/** + * + * @author JPEXS + */ +public class GraphVizDotCommands { + + public static boolean graphVizAvailable() { + String dotPath = Configuration.graphVizDotLocation.get(); + if (dotPath.isEmpty() || !new File(dotPath).exists()) { + return false; + } + return true; + } + + private static void runCommand(String command) { + try { + Process process = Runtime.getRuntime().exec(command); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String s; + while ((s = reader.readLine()) != null) { + + } + } catch (IOException e) { + //ignore + } + } + + private static boolean runDotCommand(String command) { + String dotLocation = Configuration.graphVizDotLocation.get(); + if (dotLocation.isEmpty() && !new File(dotLocation).exists()) { + return false; + }// + runCommand("\"" + dotLocation + "\" " + command); + return true; + } + + public BufferedImage dotToImage(String text) throws IOException { + File gvFile = File.createTempFile("graphexport", ".gv"); + File pngFile = File.createTempFile("graphexport", ".png"); + + PrintWriter pw = new PrintWriter(gvFile); + pw.println(text); + pw.close(); + String extraParams = " -Nfontname=times-bold -Nfontsize=12"; + if (!runDotCommand("-Tpng" + extraParams + " -o \"" + pngFile.getAbsolutePath() + "\" \"" + gvFile.getAbsolutePath() + "\"")) { + gvFile.delete(); + return null; + } + if (!pngFile.exists()) { + throw new IOException("Dot did not produce any file"); + } + BufferedImage ret = ImageIO.read(pngFile); + gvFile.delete(); + pngFile.delete(); + return ret; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/graph/GraphVizGraphPanel.java b/src/com/jpexs/decompiler/flash/gui/graph/GraphVizGraphPanel.java new file mode 100644 index 000000000..8eff56105 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/graph/GraphVizGraphPanel.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 Jindra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.graph; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.exporters.script.PcodeGraphVizExporter; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.helpers.StringBuilderTextWriter; +import com.jpexs.decompiler.graph.Graph; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +/** + * + * @author JPEXS + */ +public class GraphVizGraphPanel extends AbstractGraphPanel { + + private static final Logger logger = Logger.getLogger(GraphVizGraphPanel.class.getName()); + private BufferedImage image; + private JPanel imagePanel; + + public GraphVizGraphPanel(Graph graph) throws InterruptedException { + super(graph); + PcodeGraphVizExporter ex = new PcodeGraphVizExporter(); + StringBuilder sb = new StringBuilder(); + StringBuilderTextWriter sbWriter = new StringBuilderTextWriter(null, sb); + ex.export(graph, sbWriter); + try { + image = new GraphVizDotCommands().dotToImage(sb.toString()); + } catch (IOException ex1) { + logger.log(Level.SEVERE, "Exporting image failed", ex1); + image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + } + setLayout(new BorderLayout()); + imagePanel = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + g.drawImage(image, 0, 0, null); + } + }; + Dimension dim = new Dimension(image.getWidth(), image.getHeight()); + imagePanel.setPreferredSize(dim); + imagePanel.setMinimumSize(dim); + setPreferredSize(dim); + + setLayout(new GridBagLayout()); + add(imagePanel, new GridBagConstraints()); + setBackground(Color.white); + } + + public static boolean isAvailable() { + return GraphVizDotCommands.graphVizAvailable(); + } + +} diff --git a/src/com/jpexs/decompiler/flash/gui/locales/GraphDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/GraphDialog.properties index 6c5c8115a..fa1ba5fa5 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/GraphDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/GraphDialog.properties @@ -14,3 +14,5 @@ # along with this program. If not, see . graph = Graph +graph.better.dot = Tip: Configure GraphViz Dot executable path in Advanced settings / Paths (5) to get a way better graphs! +menu.copygraph.gv = Copy GraphViz source to ClipBoard \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 5d7785652..fcfeee3fa 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -772,6 +772,3 @@ message.font.setadvancevalues = This operation will set advance of ALL character menu.tools.deobfuscation.renameColliding = Rename colliding traits/classes filter.iggy = Iggy files (*.iggy) - -#after 11.0.0 -button.copygraph = Copy GraphViz to ClipBoard