From fd7b9b14f8d7172e6897548b8149ba1624352d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sat, 4 Nov 2023 19:09:17 +0100 Subject: [PATCH] Added Status bar with info about edges on walking shaperecords --- CHANGELOG.md | 1 + .../xfl/shapefixer/PathConverterApp.java | 128 ++++++++++++++++++ .../decompiler/flash/gui/ImagePanel.java | 19 +++ .../decompiler/flash/gui/PreviewPanel.java | 101 +++++++++++++- .../flash/gui/locales/MainFrame.properties | 14 +- .../flash/gui/locales/MainFrame_cs.properties | 12 +- .../gui/player/MediaDisplayListener.java | 6 +- .../flash/gui/player/PlayerControls.java | 28 +++- 8 files changed, 297 insertions(+), 12 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/PathConverterApp.java diff --git a/CHANGELOG.md b/CHANGELOG.md index df09ee5a3..15781701b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file. - SVG Export/Import - retain bitmap fill smoothed attribute - Export Morphshape as start and end shape (SVG, PNG, BMP) - Directory selection dialog in directory configs in advanced settings +- Status bar with info about edges on walking shaperecords ### Fixed - [#1306], [#1768] Maximizing window on other than main monitor diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/PathConverterApp.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/PathConverterApp.java new file mode 100644 index 000000000..3bcbb6ce9 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/PathConverterApp.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010-2023 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.xfl.shapefixer; + +import java.awt.Container; +import java.awt.FlowLayout; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.JFrame; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +/** + * App that converts between FLA and SVG paths. + * @author JPEXS + */ +public class PathConverterApp { + + private static boolean updating = false; + + public static void main(String[] args) { + JFrame fr = new JFrame(); + fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + fr.setSize(800, 800); + Container cnt = fr.getContentPane(); + cnt.setLayout(new FlowLayout()); + JTextArea t1 = new JTextArea(20, 50); + JTextArea t2 = new JTextArea(20, 50); + t1.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + + private void update() { + if (updating) { + return; + } + updating = true; + //", ""); + + + StringBuffer resultString = new StringBuffer(); + Pattern regex = Pattern.compile("#([A-F0-9]+)\\.([A-F0-9]+)"); + Matcher m = regex.matcher(newText); + while (m.find()) { + int p1 = Integer.parseInt(m.group(1), 16); + int p2 = Integer.parseInt(m.group(2), 16); + + if ((p1 & 0x800000) > 0) { + p1 = 0xFF000000 | p1; + } + + DecimalFormat df = new DecimalFormat("0.##", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); + df.setGroupingUsed(false); + String strValue = "" + df.format((double) p1 + p2 / 256.0); + m.appendReplacement(resultString, strValue); + } + m.appendTail(resultString); + + newText = resultString.toString(); + + t2.setText(newText.replace("!", "M").replace("|", "L").replace("[", "Q")); + updating = false; + } + }); + t2.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + + private void update() { + if (updating) { + return; + } + updating = true; + t1.setText(t2.getText().replace("M", "!").replace("L", "|").replace("Q", "[")); + updating = false; + } + }); + cnt.add(new JScrollPane(t1)); + cnt.add(new JScrollPane(t2)); + fr.setVisible(true); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index 1c3cdecd6..5d9f40a45 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -345,6 +345,12 @@ public final class ImagePanel extends JPanel implements MediaDisplay { listener.pointsUpdated(points); } } + + private void fireStatusChanged(String status) { + for (MediaDisplayListener listener : listeners) { + listener.statusChanged(status); + } + } private boolean fireEdgeSplit(int position, double splitPoint) { boolean result = true; @@ -429,6 +435,14 @@ public final class ImagePanel extends JPanel implements MediaDisplay { pointEditPanel.setVisible(false); redraw(); } + + public void setStatus(String status) { + fireStatusChanged(status); + } + + public void setNoStatus() { + fireStatusChanged(""); + } public void addBoundsChangeListener(BoundsChangeListener listener) { boundsChangeListeners.add(listener); @@ -2809,6 +2823,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { rewind(); redraw(); fireMediaDisplayStateChanged(); + fireStatusChanged(""); } @Override @@ -3501,6 +3516,10 @@ public final class ImagePanel extends JPanel implements MediaDisplay { soundPlayers.remove(sp); } } + + @Override + public void statusChanged(String status) { + } }); synchronized (ImagePanel.this) { diff --git a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java index 3604f19a0..de2951f1d 100644 --- a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java @@ -580,6 +580,14 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel createAndRunTempSwf(currentItem); } + private void setStatus(String status) { + imagePlayControls.setStatus(status); + } + + private void setNoStatus() { + setStatus(""); + } + private JPanel createImagesCard() { JPanel shapesCard = new JPanel(new BorderLayout()); JPanel previewPanel = new JPanel(new BorderLayout()); @@ -1180,10 +1188,15 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel //placeSplitPane.setDividerLocation(800); displayEditTagCard.add(createDisplayEditTagButtonsPanel(), BorderLayout.SOUTH); - ((GenericTagTreePanel) displayEditGenericPanel).addTreeSelectionListener(new TreeSelectionListener() { + ((GenericTagTreePanel) displayEditGenericPanel).addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { - JTree tree = (JTree) e.getSource(); + if (e.getNewLeadSelectionPath() == null) { + displayEditImagePanel.setStatus(""); + displayEditImagePanel.setHilightedEdge(null); + return; + } + JTree tree = (JTree) e.getSource(); Object obj = e.getPath().getLastPathComponent(); if (obj instanceof GenericTagTreePanel.FieldNode) { GenericTagTreePanel.FieldNode fieldNode = (GenericTagTreePanel.FieldNode) obj; @@ -1196,17 +1209,45 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel int x = 0; int y = 0; TreeModel model = tree.getModel(); + int fillStyle0 = 0; + int fillStyle1 = 0; + int lineStyle = 0; + int stylesIndex = -1; for (int i = 0; i < model.getChildCount(parent); i++) { Object child = model.getChild(parent, i); GenericTagTreePanel.FieldNode childFN = (GenericTagTreePanel.FieldNode) child; SHAPERECORD rec = (SHAPERECORD) childFN.getValue(0); + if (rec instanceof StyleChangeRecord) { + StyleChangeRecord scr = (StyleChangeRecord) rec; + if (scr.stateNewStyles) { + fillStyle0 = 0; + fillStyle1 = 0; + lineStyle = 0; + stylesIndex++; + } + if (scr.stateFillStyle0) { + fillStyle0 = scr.fillStyle0; + } + if (scr.stateFillStyle1) { + fillStyle1 = scr.fillStyle1; + } + if (scr.stateLineStyle) { + lineStyle = scr.lineStyle; + } + } if (rec == val) { + String edgeStatus = ""; if (rec instanceof StraightEdgeRecord) { StraightEdgeRecord ser = (StraightEdgeRecord) rec; Point point1 = new Point(x, y); Point point2 = new Point(x + ser.deltaX, y + ser.deltaY); Point[] hilightedPoint = new Point[]{point1, point2}; displayEditImagePanel.setHilightedEdge(hilightedPoint); + edgeStatus = AppStrings.translate("shaperecords.edge.straight") + .replace("%x1%", "" + point1.x) + .replace("%y1%", "" + point1.y) + .replace("%x2%", "" + point2.x) + .replace("%y2%", "" + point2.y); } else if (rec instanceof CurvedEdgeRecord) { CurvedEdgeRecord cer = (CurvedEdgeRecord) rec; Point point1 = new Point(x, y); @@ -1214,34 +1255,86 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel Point point3 = new Point(x + cer.controlDeltaX + cer.anchorDeltaX, y + cer.controlDeltaY + cer.anchorDeltaY); Point[] hilightedPoint = new Point[]{point1, point2, point3}; displayEditImagePanel.setHilightedEdge(hilightedPoint); + edgeStatus = AppStrings.translate("shaperecords.edge.curved") + .replace("%x1%", "" + point1.x) + .replace("%y1%", "" + point1.y) + .replace("%x2%", "" + point2.x) + .replace("%y2%", "" + point2.y) + .replace("%x3%", "" + point3.x) + .replace("%y3%", "" + point3.y); } else if (rec instanceof StyleChangeRecord) { - StyleChangeRecord scr = (StyleChangeRecord) rec; + StyleChangeRecord scr = (StyleChangeRecord) rec; + List styleStatusParts = new ArrayList<>(); if (scr.stateMoveTo) { Point point1 = new Point(scr.moveDeltaX, scr.moveDeltaY); Point[] hilightedPoint = new Point[]{point1}; displayEditImagePanel.setHilightedEdge(hilightedPoint); + styleStatusParts.add(AppStrings.translate("shaperecords.edge.style.move") + .replace("%x%", "" + point1.x) + .replace("%y%", "" + point1.y)); } else { Point point1 = new Point(x, y); Point[] hilightedPoint = new Point[]{point1}; - displayEditImagePanel.setHilightedEdge(hilightedPoint); + displayEditImagePanel.setHilightedEdge(hilightedPoint); } + if (scr.stateNewStyles) { + int shapeNum = 0; + if (displayEditTag instanceof ShapeTag) { + shapeNum = ((ShapeTag) displayEditTag).getShapeNum(); + } + if (displayEditTag instanceof MorphShapeTag) { + shapeNum = ((MorphShapeTag) displayEditTag).getShapeNum(); + if (shapeNum == 2) { + shapeNum = 3; + } else { + shapeNum = 1; + } + } + styleStatusParts.add(AppStrings.translate("shaperecords.edge.style.newstyles") + .replace("%numfillstyles%", "" + scr.fillStyles.fillStyles.length) + .replace("%numlinestyles%", "" + (shapeNum < 3 ? scr.lineStyles.lineStyles.length : scr.lineStyles.lineStyles2.length)) + ); + } + if (scr.stateFillStyle0) { + styleStatusParts.add(AppStrings.translate("shaperecords.edge.style.fillstyle0") + .replace("%value%", "" + scr.fillStyle0)); + } + if (scr.stateFillStyle1) { + styleStatusParts.add(AppStrings.translate("shaperecords.edge.style.fillstyle1") + .replace("%value%", "" + scr.fillStyle1)); + } + String styleDetails = String.join(", ", styleStatusParts); + edgeStatus = AppStrings.translate("shaperecords.edge.style").replace("%details%", styleDetails); } else if (rec instanceof EndShapeRecord) { Point point1 = new Point(x, y); Point[] hilightedPoint = new Point[]{point1}; displayEditImagePanel.setHilightedEdge(hilightedPoint); + edgeStatus = AppStrings.translate("shaperecords.edge.end"); } else { displayEditImagePanel.setHilightedEdge(null); + displayEditImagePanel.setStatus(""); + break; } + + String status = AppStrings.translate("shaperecords.status") + .replace("%fillstyle0%", "" + fillStyle0) + .replace("%fillstyle1%", "" + fillStyle1) + .replace("%linestyle%", "" + lineStyle) + .replace("%stylesindex%", "" + stylesIndex) + .replace("%edge%", edgeStatus); + displayEditImagePanel.setStatus(status); break; } x = rec.changeX(x); y = rec.changeY(y); } } else { + displayEditImagePanel.setStatus(""); displayEditImagePanel.setHilightedEdge(null); } } else { + displayEditImagePanel.setStatus(""); displayEditImagePanel.setHilightedEdge(null); } } diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 4e3052553..e64a0376b 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1209,4 +1209,16 @@ tag.morphshape.create = Create morphshape from shape files... dialog.morphshape.startShape = Select start shape. dialog.morphshape.endShape = Select end shape. Click cancel to make end shape same as the start shape. -error.morphshape.incompatible = Cannot create morphshape: Start and end shape have incompatible fill/line styles. \ No newline at end of file +error.morphshape.incompatible = Cannot create morphshape: Start and end shape have incompatible fill/line styles. + +shaperecords.status = FillStyle0: %fillstyle0%, FillStyle1: %fillstyle1%, LineStyle: %linestyle%, StylesIndex %stylesindex%, %edge% +shaperecords.edge.straight = Straight edge from %x1%, %y1% to %x2%, %y2% +shaperecords.edge.curved = Curved edge from %x1%, %y1% control %x2%, %y2% anchor %x3%, %y3% +shaperecords.edge.style = Style change (%details%) +shaperecords.edge.style.move = Move to %x%, %y% +shaperecords.edge.style.newstyles = New styles - %numfillstyles%x fillstyle + %numlinestyles%x linestyle +shaperecords.edge.style.fillstyle0 = FillStyle0 = %value% +shaperecords.edge.style.fillstyle1 = FillStyle1 = %value% +shaperecords.edge.end = Shape end + + diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index 600ffc4b1..aa1c4fb73 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -1186,4 +1186,14 @@ tag.morphshape.create = Vytvo\u0159it morphshape ze soubor\u016f tvar\u016f... dialog.morphshape.startShape = Vyberte po\u010d\u00e1te\u010dn\u00ed tvar. dialog.morphshape.endShape = Vyberte koncov\u00fd tvar. Klikn\u011bte na Storno pro nastaven\u00ed stejn\u00e9ho jako je po\u010d\u00e1te\u010dn\u00ed. -error.morphshape.incompatible = Nelze vytvo\u0159it morphshape: Po\u010d\u00e1te\u010dn\u00ed a koncov\u00fd tvar maj\u00ed nekompatibiln\u00ed styl v\u00fdpln\u011b/ohrani\u010den\u00ed. \ No newline at end of file +error.morphshape.incompatible = Nelze vytvo\u0159it morphshape: Po\u010d\u00e1te\u010dn\u00ed a koncov\u00fd tvar maj\u00ed nekompatibiln\u00ed styl v\u00fdpln\u011b/ohrani\u010den\u00ed. + +shaperecords.status = FillStyle0: %fillstyle0%, FillStyle1: %fillstyle1%, LineStyle: %linestyle%, IndexStylu %stylesindex%, %edge% +shaperecords.edge.straight = Rovn\u00e1 hrana z %x1%, %y1% do %x2%, %y2% +shaperecords.edge.curved = Zaoblen\u00e1 hrana z %x1%, %y1% control %x2%, %y2% anchor %x3%, %y3% +shaperecords.edge.style = Zm\u011bna stylu (%details%) +shaperecords.edge.style.move = P\u0159esun na %x%, %y% +shaperecords.edge.style.newstyles = Nov\u00e9 styly - %numfillstyles%x fillstyle + %numlinestyles%x linestyle +shaperecords.edge.style.fillstyle0 = FillStyle0 = %value% +shaperecords.edge.style.fillstyle1 = FillStyle1 = %value% +shaperecords.edge.end = Konec tvaru \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/player/MediaDisplayListener.java b/src/com/jpexs/decompiler/flash/gui/player/MediaDisplayListener.java index f97d02b17..14f7b6a5c 100644 --- a/src/com/jpexs/decompiler/flash/gui/player/MediaDisplayListener.java +++ b/src/com/jpexs/decompiler/flash/gui/player/MediaDisplayListener.java @@ -22,7 +22,9 @@ package com.jpexs.decompiler.flash.gui.player; */ public interface MediaDisplayListener { - void mediaDisplayStateChanged(MediaDisplay source); + public void mediaDisplayStateChanged(MediaDisplay source); - void playingFinished(MediaDisplay source); + public void playingFinished(MediaDisplay source); + + public void statusChanged(String status); } diff --git a/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java b/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java index d7d366a69..556988a70 100644 --- a/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java +++ b/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java @@ -116,6 +116,8 @@ public class PlayerControls extends JPanel implements MediaDisplayListener { private final JToggleButton freezeButton; private final JToggleButton muteButton; + + private final JTextField statusTextField; public static final int ZOOM_DECADE_STEPS = 10; @@ -131,7 +133,7 @@ public class PlayerControls extends JPanel implements MediaDisplayListener { private final int zeroCharacterWidth; - private final double MAX_ZOOM = 1.0e6; //in larger zooms, flash viewer stops working + private final double MAX_ZOOM = 1.0e6; //in larger zooms, flash viewer stops working static { Font font = new JLabel().getFont(); @@ -144,7 +146,15 @@ public class PlayerControls extends JPanel implements MediaDisplayListener { public PlayerControls(final MainPanel mainPanel, MediaDisplay display, JPanel middleButtonsPanel) { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - + + statusTextField = new JTextField(50); + statusTextField.setEditable(false); + statusTextField.setBorder(null); + statusTextField.setBackground(null); + //statusTextField.setVisible(true); + statusTextField.setOpaque(false); + add(statusTextField); + graphicControls = new JPanel(new BorderLayout()); JPanel graphicButtonsPanel = new JPanel(new FlowLayout()); JButton selectColorButton = new JButton(View.getIcon("color16")); @@ -330,11 +340,16 @@ public class PlayerControls extends JPanel implements MediaDisplayListener { }); playbackControls.add(progress); playbackControls.add(controlPanel); - - add(playbackControls); + + add(playbackControls); this.display.addEventListener(this); } + public void setStatus(String status) { + statusTextField.setText(status); + //statusTextField.setVisible(!status.isEmpty()); + } + private String formatMs(long ms) { long s = ms / 1000; ms %= 1000; @@ -629,6 +644,11 @@ public class PlayerControls extends JPanel implements MediaDisplayListener { public void playingFinished(MediaDisplay source) { } + @Override + public void statusChanged(String status) { + setStatus(status); + } + private class TransferableImage implements Transferable { Image img;