From f5168031dbdf9b132fbdd94b6ae3de38df8ae95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 14 Jun 2015 18:12:02 +0200 Subject: [PATCH] Generic tag editor - Adding FILTERs and SHAPERECORDs (instantiate abstract classes) --- .../flash/tags/DefineButtonTag.java | 6 + .../decompiler/flash/tags/DoActionTag.java | 5 + .../flash/tags/DoInitActionTag.java | 5 + .../flash/tags/PlaceObject2Tag.java | 1 - .../flash/tags/PlaceObject3Tag.java | 3 +- .../flash/tags/PlaceObject4Tag.java | 3 +- .../decompiler/flash/tags/base/ASMSource.java | 7 +- .../flash/types/BUTTONCONDACTION.java | 13 +- .../decompiler/flash/types/BUTTONRECORD.java | 10 + .../flash/types/CLIPACTIONRECORD.java | 10 +- .../flash/types/annotations/SWFType.java | 3 +- .../flash/types/filters/FILTER.java | 5 +- .../flash/types/shaperecords/SHAPERECORD.java | 976 +++++++------- .../com/jpexs/helpers/ConcreteClasses.java | 33 + .../com/jpexs/helpers/ReflectionTools.java | 14 +- .../decompiler/flash/gui/GenericTagPanel.java | 1192 ++++++++--------- .../flash/gui/GenericTagTreePanel.java | 187 ++- 17 files changed, 1319 insertions(+), 1154 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/helpers/ConcreteClasses.java diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java index db9704c3a..9a08e8223 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java @@ -432,4 +432,10 @@ public class DefineButtonTag extends ButtonTag implements ASMSource { public Tag getSourceTag() { return this; } + + @Override + public void setSourceTag(Tag t) { + //nothing + } + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoActionTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoActionTag.java index ea5d4bf0e..e714264f3 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoActionTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoActionTag.java @@ -211,4 +211,9 @@ public class DoActionTag extends Tag implements ASMSource { public Tag getSourceTag() { return this; } + + @Override + public void setSourceTag(Tag t) { + //nothing + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java index 0e9dd1cf9..2ecd2c212 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java @@ -249,4 +249,9 @@ public class DoInitActionTag extends Tag implements CharacterIdTag, ASMSource { public Tag getSourceTag() { return this; } + + @Override + public void setSourceTag(Tag t) { + //nothing + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java index 698e5698e..6180a1afe 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java @@ -137,7 +137,6 @@ public class PlaceObject2Tag extends PlaceObjectTypeTag implements ASMSourceCont * @since SWF 5 If PlaceFlagHasClipActions, Clip Actions Data */ @Conditional("placeFlagHasClipActions") - @HideInRawEdit //TODO: make editable public CLIPACTIONS clipActions; /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java index 2d9bb0767..b322a2447 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java @@ -33,6 +33,7 @@ import com.jpexs.decompiler.flash.types.annotations.Conditional; import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; import com.jpexs.decompiler.flash.types.annotations.Internal; import com.jpexs.decompiler.flash.types.annotations.Reserved; +import com.jpexs.decompiler.flash.types.annotations.SWFArray; import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.filters.FILTER; import com.jpexs.helpers.ByteArrayRange; @@ -185,6 +186,7 @@ public class PlaceObject3Tag extends PlaceObjectTypeTag implements ASMSourceCont * If PlaceFlagHasFilterList, List of filters on this object */ @Conditional("placeFlagHasFilterList") + @SWFArray("filter") public List surfaceFilterList; /** @@ -206,7 +208,6 @@ public class PlaceObject3Tag extends PlaceObjectTypeTag implements ASMSourceCont * @since SWF 5 If PlaceFlagHasClipActions, Clip Actions Data */ @Conditional(value = "placeFlagHasClipActions", minSwfVersion = 5) - @HideInRawEdit //TODO: make editable public CLIPACTIONS clipActions; /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java index 1c9ae79bf..11be22017 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java @@ -33,6 +33,7 @@ import com.jpexs.decompiler.flash.types.annotations.Conditional; import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; import com.jpexs.decompiler.flash.types.annotations.Internal; import com.jpexs.decompiler.flash.types.annotations.Reserved; +import com.jpexs.decompiler.flash.types.annotations.SWFArray; import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.filters.FILTER; import com.jpexs.helpers.ByteArrayRange; @@ -185,6 +186,7 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont * If PlaceFlagHasFilterList, List of filters on this object */ @Conditional("placeFlagHasFilterList") + @SWFArray("filter") public List surfaceFilterList; /** @@ -206,7 +208,6 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont * @since SWF 5 If PlaceFlagHasClipActions, Clip Actions Data */ @Conditional(value = "placeFlagHasClipActions", minSwfVersion = 5) - @HideInRawEdit //TODO: make editable public CLIPACTIONS clipActions; /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ASMSource.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ASMSource.java index 5629e6e33..8fdf1ee41 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ASMSource.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ASMSource.java @@ -12,10 +12,12 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.tags.base; import com.jpexs.decompiler.flash.DisassemblyListener; +import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.ActionList; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; @@ -84,4 +86,7 @@ public interface ASMSource extends Exportable { public String removePrefixAndSuffix(String source); public Tag getSourceTag(); + + public void setSourceTag(Tag t); + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java index 64f9f99d5..e3a1bc5ce 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java @@ -45,19 +45,26 @@ import java.util.logging.Logger; */ public class BUTTONCONDACTION implements ASMSource, Serializable { - private final SWF swf; + private SWF swf; - private final Tag tag; + private Tag tag; @Override public SWF getSwf() { return swf; } - // Constructor for Generic tag editor. TODO:Handle this somehow better + // Constructor for Generic tag editor. public BUTTONCONDACTION() { swf = null; tag = null; + actionBytes = new ByteArrayRange(new byte[0]); + } + + @Override + public void setSourceTag(Tag t) { + this.tag = t; + this.swf = t.getSwf(); } public BUTTONCONDACTION(SWF swf, SWFInputStream sis, Tag tag) throws IOException { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONRECORD.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONRECORD.java index e06cb2e79..60997fe4e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONRECORD.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONRECORD.java @@ -19,8 +19,17 @@ package com.jpexs.decompiler.flash.types; import com.jpexs.decompiler.flash.tags.DefineButton2Tag; import com.jpexs.decompiler.flash.types.annotations.Conditional; import com.jpexs.decompiler.flash.types.annotations.Reserved; +import com.jpexs.decompiler.flash.types.annotations.SWFArray; import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.filters.BEVELFILTER; +import com.jpexs.decompiler.flash.types.filters.BLURFILTER; +import com.jpexs.decompiler.flash.types.filters.COLORMATRIXFILTER; +import com.jpexs.decompiler.flash.types.filters.CONVOLUTIONFILTER; +import com.jpexs.decompiler.flash.types.filters.DROPSHADOWFILTER; import com.jpexs.decompiler.flash.types.filters.FILTER; +import com.jpexs.decompiler.flash.types.filters.GLOWFILTER; +import com.jpexs.decompiler.flash.types.filters.GRADIENTBEVELFILTER; +import com.jpexs.decompiler.flash.types.filters.GRADIENTGLOWFILTER; import java.io.Serializable; import java.util.List; @@ -91,6 +100,7 @@ public class BUTTONRECORD implements Serializable { * If within DefineButton2Tag and buttonHasFilterList: List of filters on * this button */ + @SWFArray("filter") public List filterList; /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java index 3bc0bb086..2e567e2e5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java @@ -89,10 +89,10 @@ public class CLIPACTIONRECORD implements ASMSource, Serializable { }; @Internal - private final SWF swf; + private SWF swf; @Internal - private final Tag tag; + private Tag tag; // Constructor for Generic tag editor. TODO:Handle this somehow better public CLIPACTIONRECORD() { @@ -290,4 +290,10 @@ public class CLIPACTIONRECORD implements ASMSource, Serializable { public Tag getSourceTag() { return tag; } + + @Override + public void setSourceTag(Tag t) { + this.tag = t; + this.swf = t.getSwf(); + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/annotations/SWFType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/annotations/SWFType.java index e6a20c04e..7375fba62 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/annotations/SWFType.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/annotations/SWFType.java @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.types.annotations; import com.jpexs.decompiler.flash.types.BasicType; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/filters/FILTER.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/filters/FILTER.java index 72e222e60..c3a4b2db2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/filters/FILTER.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/filters/FILTER.java @@ -12,11 +12,13 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.types.filters; import com.jpexs.decompiler.flash.types.BasicType; import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.helpers.ConcreteClasses; import com.jpexs.helpers.SerializableImage; import java.io.Serializable; @@ -25,6 +27,7 @@ import java.io.Serializable; * * @author JPEXS */ +@ConcreteClasses({BLURFILTER.class, GRADIENTBEVELFILTER.class, GLOWFILTER.class, GRADIENTGLOWFILTER.class, COLORMATRIXFILTER.class, CONVOLUTIONFILTER.class, BEVELFILTER.class, DROPSHADOWFILTER.class}) public abstract class FILTER implements Serializable { /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java index 05097efa2..d4f16144d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java @@ -1,487 +1,489 @@ -/* - * Copyright (C) 2010-2015 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.types.shaperecords; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; -import com.jpexs.decompiler.flash.helpers.FontHelper; -import com.jpexs.decompiler.flash.tags.base.NeedsCharacters; -import com.jpexs.decompiler.flash.tags.base.TextTag; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.MATRIX; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.RGB; -import com.jpexs.decompiler.flash.types.RGBA; -import com.jpexs.decompiler.flash.types.SHAPE; -import com.jpexs.helpers.SerializableImage; -import java.awt.Color; -import java.awt.Font; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.font.GlyphVector; -import java.awt.geom.AffineTransform; -import java.awt.geom.PathIterator; -import java.awt.geom.Point2D; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -/** - * - * @author JPEXS - */ -public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Serializable { - - public static final int MAX_CHARACTERS_IN_FONT_PREVIEW = 400; - - public static final boolean DRAW_BOUNDING_BOX = false; - - public abstract void calculateBits(); - - @Override - public void getNeededCharacters(Set needed) { - } - - @Override - public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { - return false; - } - - @Override - public boolean removeCharacter(int characterId) { - return false; - } - - public abstract int changeX(int x); - - public abstract int changeY(int y); - - public abstract void flip(); - - public static RECT getBounds(List records) { - int x = 0; - int y = 0; - int max_x = 0; - int max_y = 0; - int min_x = Integer.MAX_VALUE; - int min_y = Integer.MAX_VALUE; - boolean started = false; - for (SHAPERECORD r : records) { - if (r instanceof CurvedEdgeRecord) { - CurvedEdgeRecord curverEdge = (CurvedEdgeRecord) r; - int x2 = x + curverEdge.controlDeltaX; - int y2 = y + curverEdge.controlDeltaY; - if (x2 > max_x) { - max_x = x2; - } - if (y2 > max_y) { - max_y = y2; - } - if (started) { - if (y2 < min_y) { - min_y = y2; - } - if (x2 < min_x) { - min_x = x2; - } - } - } - - x = r.changeX(x); - y = r.changeY(y); - if (x > max_x) { - max_x = x; - } - if (y > max_y) { - max_y = y; - } - if (r.isMove()) { - started = true; - } - if (started) { - if (y < min_y) { - min_y = y; - } - if (x < min_x) { - min_x = x; - } - } - } - return new RECT(min_x, max_x, min_y, max_y); - } - - public static CurvedEdgeRecord straightToCurve(StraightEdgeRecord ser) { - CurvedEdgeRecord ret = new CurvedEdgeRecord(); - ret.controlDeltaX = ser.deltaX / 2; - ret.controlDeltaY = ser.deltaY / 2; - ret.anchorDeltaX = ser.deltaX - ret.controlDeltaX; - ret.anchorDeltaY = ser.deltaY - ret.controlDeltaY; - return ret; - } - - public static void shapeListToImage(SWF swf, List shapes, SerializableImage image, int frame, Color color, ColorTransform colorTransform) { - if (shapes.isEmpty()) { - return; - } - - int prevWidth = image.getWidth(); - int prevHeight = image.getHeight(); - - int maxw = 0; - int maxh = 0; - int minXMin = 0; - int minYMin = 0; - for (SHAPE s : shapes) { - RECT r = SHAPERECORD.getBounds(s.shapeRecords); - if (r.Xmax < r.Xmin || r.Ymax < r.Ymin) { - continue; - } - if (r.getWidth() > maxw) { - maxw = r.getWidth(); - } - if (r.getHeight() > maxh) { - maxh = r.getHeight(); - } - if (r.Xmin < minXMin) { - minXMin = r.Xmin; - } - if (r.Ymin < minYMin) { - minYMin = r.Ymin; - } - } - - int shapeCount = Math.min(MAX_CHARACTERS_IN_FONT_PREVIEW, shapes.size()); - int frameCount = (shapes.size() - 1) / MAX_CHARACTERS_IN_FONT_PREVIEW + 1; - if (frameCount < 1) { - frameCount = 1; - } - if (frame >= frameCount) { - frame = frameCount - 1; - } - int cols = (int) Math.ceil(Math.sqrt(shapeCount)); - int pos = frame * MAX_CHARACTERS_IN_FONT_PREVIEW; - int w2 = (int) (prevWidth * SWF.unitDivisor / cols); - int h2 = (int) (prevHeight * SWF.unitDivisor / cols); - - if (maxw == 0) { - return; - } - - int mh = maxh * w2 / maxw; - int mw; - if (mh > h2) { - mw = maxw * h2 / maxh; - mh = h2; - } else { - mw = w2; - } - - float ratio = (float) mw / (float) maxw; - - loopy: - for (int y = 0; y < cols; y++) { - for (int x = 0; x < cols; x++) { - if (pos >= shapes.size()) { - break loopy; - } - - // shapeNum: 1 - SHAPE shape = shapes.get(pos); - List records = shape.shapeRecords; - RECT bounds = SHAPERECORD.getBounds(records); - - int w1 = bounds.getWidth(); - int h1 = bounds.getHeight(); - - double w = ratio * w1; - double h = ratio * h1; - double px = x * w2 + w2 / 2 - w / 2 - minXMin * ratio; - double py = y * h2 - minYMin * ratio; - - Matrix transformation = new Matrix(); - transformation.translate(px, py); - transformation = transformation.concatenate(Matrix.getScaleInstance(ratio)); - BitmapExporter.export(swf, shape, color, image, transformation, colorTransform); - - // draw bounding boxes - if (DRAW_BOUNDING_BOX) { - RGB borderColor = new RGBA(Color.black); - RGB fillColor = new RGBA(new Color(255, 255, 255, 0)); - transformation = Matrix.getTranslateInstance(bounds.Xmin, bounds.Ymin).preConcatenate(transformation); - TextTag.drawBorder(swf, image, borderColor, fillColor, bounds, new MATRIX(), transformation, colorTransform); - } - pos++; - } - } - } - - public abstract boolean isMove(); - - public static List systemFontCharactersToSHAPES(Font font, int fontSize, String characters) { - List ret = new ArrayList<>(); - for (int i = 0; i < characters.length(); i++) { - ret.add(systemFontCharacterToSHAPE(font, fontSize, characters.charAt(i))); - } - return ret; - } - - public static SHAPE systemFontCharacterToSHAPE(Font font, int fontSize, char character) { - return fontCharacterToSHAPE(font, fontSize, character); - } - - public static SHAPE fontCharacterToSHAPE(final Font font, float fontSize, char character) { - int multiplier = 1; - if (fontSize > 1024) { - multiplier = (int) (fontSize / 1024); - fontSize = 1024; - } - List retList = new ArrayList<>(); - Font f = font.deriveFont(fontSize); - GlyphVector v = FontHelper.createGlyphVector(f, character); - Shape shp = v.getOutline(); - double[] points = new double[6]; - int lastX = 0; - int lastY = 0; - int startX = 0; - int startY = 0; - for (PathIterator it = shp.getPathIterator(null); !it.isDone(); it.next()) { - int type = it.currentSegment(points); - switch (type) { - case PathIterator.SEG_MOVETO: - StyleChangeRecord scr = new StyleChangeRecord(); - scr.stateMoveTo = true; - scr.moveDeltaX = multiplier * (int) Math.round(points[0]); - scr.moveDeltaY = multiplier * (int) Math.round(points[1]); - scr.moveBits = SWFOutputStream.getNeededBitsS(scr.moveDeltaX, scr.moveDeltaY); - retList.add(scr); - lastX = (int) Math.round(points[0]); - lastY = (int) Math.round(points[1]); - startX = lastX; - startY = lastY; - break; - case PathIterator.SEG_LINETO: - StraightEdgeRecord ser = new StraightEdgeRecord(); - ser.deltaX = multiplier * (((int) Math.round(points[0])) - lastX); - ser.deltaY = multiplier * (((int) Math.round(points[1])) - lastY); - - ser.generalLineFlag = ser.deltaX != 0 && ser.deltaY != 0; - if (ser.deltaX == 0) { - ser.vertLineFlag = true; - } - ser.numBits = SWFOutputStream.getNeededBitsS(ser.deltaX, ser.deltaY) - 2; - if (ser.numBits < 0) { - ser.numBits = 0; - } - retList.add(ser); - lastX = (int) Math.round(points[0]); - lastY = (int) Math.round(points[1]); - break; - case PathIterator.SEG_CUBICTO: - double[] cubicCoords = new double[]{ - lastX, lastY, - Math.round(points[0]), Math.round(points[1]), - Math.round(points[2]), Math.round(points[3]), - Math.round(points[4]), Math.round(points[5]) - }; - double[][] quadCoords = approximateCubic(cubicCoords); - for (int i = 0; i < quadCoords.length; i++) { - CurvedEdgeRecord cer = new CurvedEdgeRecord(); - cer.controlDeltaX = multiplier * (((int) Math.round(quadCoords[i][0])) - lastX); - cer.controlDeltaY = multiplier * (((int) Math.round(quadCoords[i][1])) - lastY); - cer.anchorDeltaX = multiplier * (((int) Math.round(quadCoords[i][2])) - ((int) Math.round(quadCoords[i][0]))); - cer.anchorDeltaY = multiplier * (((int) Math.round(quadCoords[i][3])) - ((int) Math.round(quadCoords[i][1]))); - cer.numBits = SWFOutputStream.getNeededBitsS(cer.controlDeltaX, cer.controlDeltaY, cer.anchorDeltaX, cer.anchorDeltaY) - 2; - if (cer.numBits < 0) { - cer.numBits = 0; - } - lastX = (int) Math.round(quadCoords[i][2]); - lastY = (int) Math.round(quadCoords[i][3]); - retList.add(cer); - } - break; - case PathIterator.SEG_QUADTO: - CurvedEdgeRecord cer = new CurvedEdgeRecord(); - cer.controlDeltaX = multiplier * (((int) Math.round(points[0])) - lastX); - cer.controlDeltaY = multiplier * (((int) Math.round(points[1])) - lastY); - cer.anchorDeltaX = multiplier * (((int) Math.round(points[2])) - (int) Math.round(points[0])); - cer.anchorDeltaY = multiplier * (((int) Math.round(points[3])) - (int) Math.round(points[1])); - cer.numBits = SWFOutputStream.getNeededBitsS(cer.controlDeltaX, cer.controlDeltaY, cer.anchorDeltaX, cer.anchorDeltaY) - 2; - if (cer.numBits < 0) { - cer.numBits = 0; - } - retList.add(cer); - lastX = (int) Math.round(points[2]); - lastY = (int) Math.round(points[3]); - break; - case PathIterator.SEG_CLOSE: //Closing line back to last SEG_MOVETO - if ((startX == lastX) && (startY == lastY)) { - break; - } - StraightEdgeRecord closeSer = new StraightEdgeRecord(); - closeSer.generalLineFlag = true; - closeSer.deltaX = multiplier * ((int) Math.round((startX - lastX))); - closeSer.deltaY = multiplier * ((int) Math.round((startY - lastY))); - closeSer.numBits = SWFOutputStream.getNeededBitsS(closeSer.deltaX, closeSer.deltaY) - 2; - if (closeSer.numBits < 0) { - closeSer.numBits = 0; - } - retList.add(closeSer); - lastX = startX; - lastY = startY; - break; - } - } - SHAPE shape = new SHAPE(); - StyleChangeRecord init; - if (!retList.isEmpty() && retList.get(0) instanceof StyleChangeRecord) { - init = (StyleChangeRecord) retList.get(0); - } else { - init = new StyleChangeRecord(); - retList.add(0, init); - } - - retList.add(new EndShapeRecord()); - init.stateFillStyle0 = true; - init.fillStyle0 = 1; - shape.shapeRecords = retList; - shape.numFillBits = 1; - shape.numLineBits = 0; - return shape; - } - - // Taken from org.apache.fop.afp.util - public static double[][] approximateCubic(double[] cubicControlPointCoords) { - if (cubicControlPointCoords.length < 8) { - throw new IllegalArgumentException("Must have at least 8 coordinates"); - } - - //extract point objects from source array - Point2D p0 = new Point2D.Double(cubicControlPointCoords[0], cubicControlPointCoords[1]); - Point2D p1 = new Point2D.Double(cubicControlPointCoords[2], cubicControlPointCoords[3]); - Point2D p2 = new Point2D.Double(cubicControlPointCoords[4], cubicControlPointCoords[5]); - Point2D p3 = new Point2D.Double(cubicControlPointCoords[6], cubicControlPointCoords[7]); - - //calculates the useful base points - Point2D pa = getPointOnSegment(p0, p1, 3.0 / 4.0); - Point2D pb = getPointOnSegment(p3, p2, 3.0 / 4.0); - - //get 1/16 of the [P3, P0] segment - double dx = (p3.getX() - p0.getX()) / 16.0; - double dy = (p3.getY() - p0.getY()) / 16.0; - - //calculates control point 1 - Point2D pc1 = getPointOnSegment(p0, p1, 3.0 / 8.0); - - //calculates control point 2 - Point2D pc2 = getPointOnSegment(pa, pb, 3.0 / 8.0); - pc2 = movePoint(pc2, -dx, -dy); - - //calculates control point 3 - Point2D pc3 = getPointOnSegment(pb, pa, 3.0 / 8.0); - pc3 = movePoint(pc3, dx, dy); - - //calculates control point 4 - Point2D pc4 = getPointOnSegment(p3, p2, 3.0 / 8.0); - - //calculates the 3 anchor points - Point2D pa1 = getMidPoint(pc1, pc2); - Point2D pa2 = getMidPoint(pa, pb); - Point2D pa3 = getMidPoint(pc3, pc4); - - //return the points for the four quadratic curves - return new double[][]{ - {pc1.getX(), pc1.getY(), pa1.getX(), pa1.getY()}, - {pc2.getX(), pc2.getY(), pa2.getX(), pa2.getY()}, - {pc3.getX(), pc3.getY(), pa3.getX(), pa3.getY()}, - {pc4.getX(), pc4.getY(), p3.getX(), p3.getY()}}; - } - - private static Point2D.Double movePoint(Point2D point, double dx, double dy) { - return new Point2D.Double(point.getX() + dx, point.getY() + dy); - } - - private static Point2D getMidPoint(Point2D p0, Point2D p1) { - return getPointOnSegment(p0, p1, 0.5); - } - - private static Point2D getPointOnSegment(Point2D p0, Point2D p1, double ratio) { - double x = p0.getX() + ((p1.getX() - p0.getX()) * ratio); - double y = p0.getY() + ((p1.getY() - p0.getY()) * ratio); - return new Point2D.Double(x, y); - } - - public static SHAPE resizeSHAPE(SHAPE shp, double multiplier) { - SHAPE ret = new SHAPE(); - ret.numFillBits = shp.numFillBits; - ret.numLineBits = shp.numLineBits; - List recs = new ArrayList<>(); - for (SHAPERECORD r : shp.shapeRecords) { - SHAPERECORD c = r.clone(); - if (c instanceof StyleChangeRecord) { - StyleChangeRecord scr = (StyleChangeRecord) c; - scr.moveDeltaX = (int) (multiplier * scr.moveDeltaX); - scr.moveDeltaY = (int) (multiplier * scr.moveDeltaY); - scr.calculateBits(); - } - if (c instanceof CurvedEdgeRecord) { - CurvedEdgeRecord cer = (CurvedEdgeRecord) c; - cer.controlDeltaX = (int) (multiplier * cer.controlDeltaX); - cer.controlDeltaY = (int) (multiplier * cer.controlDeltaY); - cer.anchorDeltaX = (int) (multiplier * cer.anchorDeltaX); - cer.anchorDeltaY = (int) (multiplier * cer.anchorDeltaY); - cer.calculateBits(); - } - if (c instanceof StraightEdgeRecord) { - StraightEdgeRecord ser = (StraightEdgeRecord) c; - ser.deltaX = (int) (multiplier * ser.deltaX); - ser.deltaY = (int) (multiplier * ser.deltaY); - ser.calculateBits(); - } - recs.add(c); - } - ret.shapeRecords = recs; - return ret; - } - - public static Shape moveShapeToStart(Shape s) { - Rectangle bds = s.getBounds(); - s = AffineTransform.getTranslateInstance(-bds.x, -bds.y).createTransformedShape(s); - return s; - } - - public static Shape twipToPixelShape(Shape s) { - Rectangle bds = s.getBounds(); - int dx = -bds.x - bds.width / 2; - int dy = -bds.y - bds.height / 2; - s = AffineTransform.getTranslateInstance(dx, dy).createTransformedShape(s); - s = AffineTransform.getScaleInstance(1 / SWF.unitDivisor, 1 / SWF.unitDivisor).createTransformedShape(s); - s = AffineTransform.getTranslateInstance(-dx / SWF.unitDivisor, -dy / SWF.unitDivisor).createTransformedShape(s); - return s; - } - - @Override - public SHAPERECORD clone() { - try { - return (SHAPERECORD) super.clone(); - } catch (CloneNotSupportedException ex) { - throw new RuntimeException(); - } - } -} +/* + * Copyright (C) 2010-2015 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.types.shaperecords; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; +import com.jpexs.decompiler.flash.helpers.FontHelper; +import com.jpexs.decompiler.flash.tags.base.NeedsCharacters; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.SHAPE; +import com.jpexs.helpers.ConcreteClasses; +import com.jpexs.helpers.SerializableImage; +import java.awt.Color; +import java.awt.Font; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * + * @author JPEXS + */ +@ConcreteClasses({CurvedEdgeRecord.class, StraightEdgeRecord.class, StyleChangeRecord.class, EndShapeRecord.class}) +public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Serializable { + + public static final int MAX_CHARACTERS_IN_FONT_PREVIEW = 400; + + public static final boolean DRAW_BOUNDING_BOX = false; + + public abstract void calculateBits(); + + @Override + public void getNeededCharacters(Set needed) { + } + + @Override + public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { + return false; + } + + @Override + public boolean removeCharacter(int characterId) { + return false; + } + + public abstract int changeX(int x); + + public abstract int changeY(int y); + + public abstract void flip(); + + public static RECT getBounds(List records) { + int x = 0; + int y = 0; + int max_x = 0; + int max_y = 0; + int min_x = Integer.MAX_VALUE; + int min_y = Integer.MAX_VALUE; + boolean started = false; + for (SHAPERECORD r : records) { + if (r instanceof CurvedEdgeRecord) { + CurvedEdgeRecord curverEdge = (CurvedEdgeRecord) r; + int x2 = x + curverEdge.controlDeltaX; + int y2 = y + curverEdge.controlDeltaY; + if (x2 > max_x) { + max_x = x2; + } + if (y2 > max_y) { + max_y = y2; + } + if (started) { + if (y2 < min_y) { + min_y = y2; + } + if (x2 < min_x) { + min_x = x2; + } + } + } + + x = r.changeX(x); + y = r.changeY(y); + if (x > max_x) { + max_x = x; + } + if (y > max_y) { + max_y = y; + } + if (r.isMove()) { + started = true; + } + if (started) { + if (y < min_y) { + min_y = y; + } + if (x < min_x) { + min_x = x; + } + } + } + return new RECT(min_x, max_x, min_y, max_y); + } + + public static CurvedEdgeRecord straightToCurve(StraightEdgeRecord ser) { + CurvedEdgeRecord ret = new CurvedEdgeRecord(); + ret.controlDeltaX = ser.deltaX / 2; + ret.controlDeltaY = ser.deltaY / 2; + ret.anchorDeltaX = ser.deltaX - ret.controlDeltaX; + ret.anchorDeltaY = ser.deltaY - ret.controlDeltaY; + return ret; + } + + public static void shapeListToImage(SWF swf, List shapes, SerializableImage image, int frame, Color color, ColorTransform colorTransform) { + if (shapes.isEmpty()) { + return; + } + + int prevWidth = image.getWidth(); + int prevHeight = image.getHeight(); + + int maxw = 0; + int maxh = 0; + int minXMin = 0; + int minYMin = 0; + for (SHAPE s : shapes) { + RECT r = SHAPERECORD.getBounds(s.shapeRecords); + if (r.Xmax < r.Xmin || r.Ymax < r.Ymin) { + continue; + } + if (r.getWidth() > maxw) { + maxw = r.getWidth(); + } + if (r.getHeight() > maxh) { + maxh = r.getHeight(); + } + if (r.Xmin < minXMin) { + minXMin = r.Xmin; + } + if (r.Ymin < minYMin) { + minYMin = r.Ymin; + } + } + + int shapeCount = Math.min(MAX_CHARACTERS_IN_FONT_PREVIEW, shapes.size()); + int frameCount = (shapes.size() - 1) / MAX_CHARACTERS_IN_FONT_PREVIEW + 1; + if (frameCount < 1) { + frameCount = 1; + } + if (frame >= frameCount) { + frame = frameCount - 1; + } + int cols = (int) Math.ceil(Math.sqrt(shapeCount)); + int pos = frame * MAX_CHARACTERS_IN_FONT_PREVIEW; + int w2 = (int) (prevWidth * SWF.unitDivisor / cols); + int h2 = (int) (prevHeight * SWF.unitDivisor / cols); + + if (maxw == 0) { + return; + } + + int mh = maxh * w2 / maxw; + int mw; + if (mh > h2) { + mw = maxw * h2 / maxh; + mh = h2; + } else { + mw = w2; + } + + float ratio = (float) mw / (float) maxw; + + loopy: + for (int y = 0; y < cols; y++) { + for (int x = 0; x < cols; x++) { + if (pos >= shapes.size()) { + break loopy; + } + + // shapeNum: 1 + SHAPE shape = shapes.get(pos); + List records = shape.shapeRecords; + RECT bounds = SHAPERECORD.getBounds(records); + + int w1 = bounds.getWidth(); + int h1 = bounds.getHeight(); + + double w = ratio * w1; + double h = ratio * h1; + double px = x * w2 + w2 / 2 - w / 2 - minXMin * ratio; + double py = y * h2 - minYMin * ratio; + + Matrix transformation = new Matrix(); + transformation.translate(px, py); + transformation = transformation.concatenate(Matrix.getScaleInstance(ratio)); + BitmapExporter.export(swf, shape, color, image, transformation, colorTransform); + + // draw bounding boxes + if (DRAW_BOUNDING_BOX) { + RGB borderColor = new RGBA(Color.black); + RGB fillColor = new RGBA(new Color(255, 255, 255, 0)); + transformation = Matrix.getTranslateInstance(bounds.Xmin, bounds.Ymin).preConcatenate(transformation); + TextTag.drawBorder(swf, image, borderColor, fillColor, bounds, new MATRIX(), transformation, colorTransform); + } + pos++; + } + } + } + + public abstract boolean isMove(); + + public static List systemFontCharactersToSHAPES(Font font, int fontSize, String characters) { + List ret = new ArrayList<>(); + for (int i = 0; i < characters.length(); i++) { + ret.add(systemFontCharacterToSHAPE(font, fontSize, characters.charAt(i))); + } + return ret; + } + + public static SHAPE systemFontCharacterToSHAPE(Font font, int fontSize, char character) { + return fontCharacterToSHAPE(font, fontSize, character); + } + + public static SHAPE fontCharacterToSHAPE(final Font font, float fontSize, char character) { + int multiplier = 1; + if (fontSize > 1024) { + multiplier = (int) (fontSize / 1024); + fontSize = 1024; + } + List retList = new ArrayList<>(); + Font f = font.deriveFont(fontSize); + GlyphVector v = FontHelper.createGlyphVector(f, character); + Shape shp = v.getOutline(); + double[] points = new double[6]; + int lastX = 0; + int lastY = 0; + int startX = 0; + int startY = 0; + for (PathIterator it = shp.getPathIterator(null); !it.isDone(); it.next()) { + int type = it.currentSegment(points); + switch (type) { + case PathIterator.SEG_MOVETO: + StyleChangeRecord scr = new StyleChangeRecord(); + scr.stateMoveTo = true; + scr.moveDeltaX = multiplier * (int) Math.round(points[0]); + scr.moveDeltaY = multiplier * (int) Math.round(points[1]); + scr.moveBits = SWFOutputStream.getNeededBitsS(scr.moveDeltaX, scr.moveDeltaY); + retList.add(scr); + lastX = (int) Math.round(points[0]); + lastY = (int) Math.round(points[1]); + startX = lastX; + startY = lastY; + break; + case PathIterator.SEG_LINETO: + StraightEdgeRecord ser = new StraightEdgeRecord(); + ser.deltaX = multiplier * (((int) Math.round(points[0])) - lastX); + ser.deltaY = multiplier * (((int) Math.round(points[1])) - lastY); + + ser.generalLineFlag = ser.deltaX != 0 && ser.deltaY != 0; + if (ser.deltaX == 0) { + ser.vertLineFlag = true; + } + ser.numBits = SWFOutputStream.getNeededBitsS(ser.deltaX, ser.deltaY) - 2; + if (ser.numBits < 0) { + ser.numBits = 0; + } + retList.add(ser); + lastX = (int) Math.round(points[0]); + lastY = (int) Math.round(points[1]); + break; + case PathIterator.SEG_CUBICTO: + double[] cubicCoords = new double[]{ + lastX, lastY, + Math.round(points[0]), Math.round(points[1]), + Math.round(points[2]), Math.round(points[3]), + Math.round(points[4]), Math.round(points[5]) + }; + double[][] quadCoords = approximateCubic(cubicCoords); + for (int i = 0; i < quadCoords.length; i++) { + CurvedEdgeRecord cer = new CurvedEdgeRecord(); + cer.controlDeltaX = multiplier * (((int) Math.round(quadCoords[i][0])) - lastX); + cer.controlDeltaY = multiplier * (((int) Math.round(quadCoords[i][1])) - lastY); + cer.anchorDeltaX = multiplier * (((int) Math.round(quadCoords[i][2])) - ((int) Math.round(quadCoords[i][0]))); + cer.anchorDeltaY = multiplier * (((int) Math.round(quadCoords[i][3])) - ((int) Math.round(quadCoords[i][1]))); + cer.numBits = SWFOutputStream.getNeededBitsS(cer.controlDeltaX, cer.controlDeltaY, cer.anchorDeltaX, cer.anchorDeltaY) - 2; + if (cer.numBits < 0) { + cer.numBits = 0; + } + lastX = (int) Math.round(quadCoords[i][2]); + lastY = (int) Math.round(quadCoords[i][3]); + retList.add(cer); + } + break; + case PathIterator.SEG_QUADTO: + CurvedEdgeRecord cer = new CurvedEdgeRecord(); + cer.controlDeltaX = multiplier * (((int) Math.round(points[0])) - lastX); + cer.controlDeltaY = multiplier * (((int) Math.round(points[1])) - lastY); + cer.anchorDeltaX = multiplier * (((int) Math.round(points[2])) - (int) Math.round(points[0])); + cer.anchorDeltaY = multiplier * (((int) Math.round(points[3])) - (int) Math.round(points[1])); + cer.numBits = SWFOutputStream.getNeededBitsS(cer.controlDeltaX, cer.controlDeltaY, cer.anchorDeltaX, cer.anchorDeltaY) - 2; + if (cer.numBits < 0) { + cer.numBits = 0; + } + retList.add(cer); + lastX = (int) Math.round(points[2]); + lastY = (int) Math.round(points[3]); + break; + case PathIterator.SEG_CLOSE: //Closing line back to last SEG_MOVETO + if ((startX == lastX) && (startY == lastY)) { + break; + } + StraightEdgeRecord closeSer = new StraightEdgeRecord(); + closeSer.generalLineFlag = true; + closeSer.deltaX = multiplier * ((int) Math.round((startX - lastX))); + closeSer.deltaY = multiplier * ((int) Math.round((startY - lastY))); + closeSer.numBits = SWFOutputStream.getNeededBitsS(closeSer.deltaX, closeSer.deltaY) - 2; + if (closeSer.numBits < 0) { + closeSer.numBits = 0; + } + retList.add(closeSer); + lastX = startX; + lastY = startY; + break; + } + } + SHAPE shape = new SHAPE(); + StyleChangeRecord init; + if (!retList.isEmpty() && retList.get(0) instanceof StyleChangeRecord) { + init = (StyleChangeRecord) retList.get(0); + } else { + init = new StyleChangeRecord(); + retList.add(0, init); + } + + retList.add(new EndShapeRecord()); + init.stateFillStyle0 = true; + init.fillStyle0 = 1; + shape.shapeRecords = retList; + shape.numFillBits = 1; + shape.numLineBits = 0; + return shape; + } + + // Taken from org.apache.fop.afp.util + public static double[][] approximateCubic(double[] cubicControlPointCoords) { + if (cubicControlPointCoords.length < 8) { + throw new IllegalArgumentException("Must have at least 8 coordinates"); + } + + //extract point objects from source array + Point2D p0 = new Point2D.Double(cubicControlPointCoords[0], cubicControlPointCoords[1]); + Point2D p1 = new Point2D.Double(cubicControlPointCoords[2], cubicControlPointCoords[3]); + Point2D p2 = new Point2D.Double(cubicControlPointCoords[4], cubicControlPointCoords[5]); + Point2D p3 = new Point2D.Double(cubicControlPointCoords[6], cubicControlPointCoords[7]); + + //calculates the useful base points + Point2D pa = getPointOnSegment(p0, p1, 3.0 / 4.0); + Point2D pb = getPointOnSegment(p3, p2, 3.0 / 4.0); + + //get 1/16 of the [P3, P0] segment + double dx = (p3.getX() - p0.getX()) / 16.0; + double dy = (p3.getY() - p0.getY()) / 16.0; + + //calculates control point 1 + Point2D pc1 = getPointOnSegment(p0, p1, 3.0 / 8.0); + + //calculates control point 2 + Point2D pc2 = getPointOnSegment(pa, pb, 3.0 / 8.0); + pc2 = movePoint(pc2, -dx, -dy); + + //calculates control point 3 + Point2D pc3 = getPointOnSegment(pb, pa, 3.0 / 8.0); + pc3 = movePoint(pc3, dx, dy); + + //calculates control point 4 + Point2D pc4 = getPointOnSegment(p3, p2, 3.0 / 8.0); + + //calculates the 3 anchor points + Point2D pa1 = getMidPoint(pc1, pc2); + Point2D pa2 = getMidPoint(pa, pb); + Point2D pa3 = getMidPoint(pc3, pc4); + + //return the points for the four quadratic curves + return new double[][]{ + {pc1.getX(), pc1.getY(), pa1.getX(), pa1.getY()}, + {pc2.getX(), pc2.getY(), pa2.getX(), pa2.getY()}, + {pc3.getX(), pc3.getY(), pa3.getX(), pa3.getY()}, + {pc4.getX(), pc4.getY(), p3.getX(), p3.getY()}}; + } + + private static Point2D.Double movePoint(Point2D point, double dx, double dy) { + return new Point2D.Double(point.getX() + dx, point.getY() + dy); + } + + private static Point2D getMidPoint(Point2D p0, Point2D p1) { + return getPointOnSegment(p0, p1, 0.5); + } + + private static Point2D getPointOnSegment(Point2D p0, Point2D p1, double ratio) { + double x = p0.getX() + ((p1.getX() - p0.getX()) * ratio); + double y = p0.getY() + ((p1.getY() - p0.getY()) * ratio); + return new Point2D.Double(x, y); + } + + public static SHAPE resizeSHAPE(SHAPE shp, double multiplier) { + SHAPE ret = new SHAPE(); + ret.numFillBits = shp.numFillBits; + ret.numLineBits = shp.numLineBits; + List recs = new ArrayList<>(); + for (SHAPERECORD r : shp.shapeRecords) { + SHAPERECORD c = r.clone(); + if (c instanceof StyleChangeRecord) { + StyleChangeRecord scr = (StyleChangeRecord) c; + scr.moveDeltaX = (int) (multiplier * scr.moveDeltaX); + scr.moveDeltaY = (int) (multiplier * scr.moveDeltaY); + scr.calculateBits(); + } + if (c instanceof CurvedEdgeRecord) { + CurvedEdgeRecord cer = (CurvedEdgeRecord) c; + cer.controlDeltaX = (int) (multiplier * cer.controlDeltaX); + cer.controlDeltaY = (int) (multiplier * cer.controlDeltaY); + cer.anchorDeltaX = (int) (multiplier * cer.anchorDeltaX); + cer.anchorDeltaY = (int) (multiplier * cer.anchorDeltaY); + cer.calculateBits(); + } + if (c instanceof StraightEdgeRecord) { + StraightEdgeRecord ser = (StraightEdgeRecord) c; + ser.deltaX = (int) (multiplier * ser.deltaX); + ser.deltaY = (int) (multiplier * ser.deltaY); + ser.calculateBits(); + } + recs.add(c); + } + ret.shapeRecords = recs; + return ret; + } + + public static Shape moveShapeToStart(Shape s) { + Rectangle bds = s.getBounds(); + s = AffineTransform.getTranslateInstance(-bds.x, -bds.y).createTransformedShape(s); + return s; + } + + public static Shape twipToPixelShape(Shape s) { + Rectangle bds = s.getBounds(); + int dx = -bds.x - bds.width / 2; + int dy = -bds.y - bds.height / 2; + s = AffineTransform.getTranslateInstance(dx, dy).createTransformedShape(s); + s = AffineTransform.getScaleInstance(1 / SWF.unitDivisor, 1 / SWF.unitDivisor).createTransformedShape(s); + s = AffineTransform.getTranslateInstance(-dx / SWF.unitDivisor, -dy / SWF.unitDivisor).createTransformedShape(s); + return s; + } + + @Override + public SHAPERECORD clone() { + try { + return (SHAPERECORD) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new RuntimeException(); + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/ConcreteClasses.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/ConcreteClasses.java new file mode 100644 index 000000000..32f0d9d3a --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/ConcreteClasses.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010-2015 JPEXS + * + * 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.helpers; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author JPEXS + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ConcreteClasses { + + Class[] value(); +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/ReflectionTools.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/ReflectionTools.java index 74c4cdfc8..16d06f44e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/helpers/ReflectionTools.java +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/ReflectionTools.java @@ -170,7 +170,7 @@ public class ReflectionTools { } @SuppressWarnings("unchecked") - public static boolean addToList(Object object, Field field, int index) { + public static boolean addToList(Object object, Field field, int index, Class cls) { if (!List.class.isAssignableFrom(field.getType())) { return false; } @@ -184,7 +184,7 @@ public class ReflectionTools { ParameterizedType listType = (ParameterizedType) field.getGenericType(); Class parameterClass = (Class) listType.getActualTypeArguments()[0]; try { - Object val = newInstanceOf(parameterClass); + Object val = newInstanceOf(cls == null ? parameterClass : cls); if (val == null) { return false; } @@ -213,7 +213,7 @@ public class ReflectionTools { return null; } - public static boolean addToArray(Object object, Field field, int index, boolean notnull) { + public static boolean addToArray(Object object, Field field, int index, boolean notnull, Class cls) { if (!field.getType().isArray()) { return false; } @@ -228,7 +228,7 @@ public class ReflectionTools { Object val = null; if (!componentClass.isPrimitive()) { try { - val = newInstanceOf(componentClass); + val = newInstanceOf(cls == null ? componentClass : cls); } catch (InstantiationException | IllegalAccessException ex) { logger.log(Level.SEVERE, null, ex); return false; @@ -261,13 +261,13 @@ public class ReflectionTools { return true; } - public static boolean addToField(Object object, Field field, int index, boolean notnull) { + public static boolean addToField(Object object, Field field, int index, boolean notnull, Class cls) { if (List.class.isAssignableFrom(field.getType())) { - return addToList(object, field, index); + return addToList(object, field, index, cls); } if (field.getType().isArray()) { - return addToArray(object, field, index, notnull); + return addToArray(object, field, index, notnull, cls); } return false; } diff --git a/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java b/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java index 045cbc82e..7f47ade65 100644 --- a/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java @@ -1,596 +1,596 @@ -/* - * Copyright (C) 2010-2015 JPEXS - * - * 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; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.gui.generictageditors.BooleanEditor; -import com.jpexs.decompiler.flash.gui.generictageditors.ChangeListener; -import com.jpexs.decompiler.flash.gui.generictageditors.ColorEditor; -import com.jpexs.decompiler.flash.gui.generictageditors.GenericTagEditor; -import com.jpexs.decompiler.flash.gui.generictageditors.NumberEditor; -import com.jpexs.decompiler.flash.gui.generictageditors.StringEditor; -import com.jpexs.decompiler.flash.gui.helpers.SpringUtilities; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.types.ARGB; -import com.jpexs.decompiler.flash.types.RGB; -import com.jpexs.decompiler.flash.types.RGBA; -import com.jpexs.decompiler.flash.types.annotations.Calculated; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.flash.types.annotations.Multiline; -import com.jpexs.decompiler.flash.types.annotations.Optional; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.parser.AnnotationParseException; -import com.jpexs.decompiler.flash.types.annotations.parser.ConditionEvaluator; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.ReflectionTools; -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.JButton; -import javax.swing.JEditorPane; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollBar; -import javax.swing.JScrollPane; -import javax.swing.SpringLayout; - -/** - * Old Generic Tag editor - * - * @author JPEXS - */ -public class GenericTagPanel extends JPanel implements ChangeListener { - - private static final Logger logger = Logger.getLogger(GenericTagPanel.class.getName()); - - private final JEditorPane genericTagPropertiesEditorPane; - - private final JPanel genericTagPropertiesEditPanel; - - private final JScrollPane genericTagPropertiesEditorPaneScrollPanel; - - private final JScrollPane genericTagPropertiesEditPanelScrollPanel; - - private Tag tag; - - private Tag editedTag; - - private List keys = new ArrayList<>(); - - private Map editors = new HashMap<>(); - - private Map labels = new HashMap<>(); - - private Map types = new HashMap<>(); - - private Map> fieldPaths = new HashMap<>(); - - private Map> fieldIndices = new HashMap<>(); - - private HeaderLabel hdr; - - private Set addKeys = new HashSet<>(); - - private Map addButtons = new HashMap<>(); - - private Map removeButtons = new HashMap<>(); - - public GenericTagPanel() { - super(new BorderLayout()); - - hdr = new HeaderLabel(""); - add(hdr, BorderLayout.NORTH); - genericTagPropertiesEditorPane = new JEditorPane() { - @Override - public boolean getScrollableTracksViewportWidth() { - return true; - } - }; - genericTagPropertiesEditorPane.setEditable(false); - genericTagPropertiesEditorPaneScrollPanel = new JScrollPane(genericTagPropertiesEditorPane); - add(genericTagPropertiesEditorPaneScrollPanel, BorderLayout.CENTER); - - genericTagPropertiesEditPanel = new JPanel(); - genericTagPropertiesEditPanel.setLayout(new SpringLayout()); - JPanel edPanel = new JPanel(new BorderLayout()); - edPanel.add(genericTagPropertiesEditPanel, BorderLayout.NORTH); - genericTagPropertiesEditPanelScrollPanel = new JScrollPane(edPanel); - } - - public void clear() { - editors.clear(); - fieldPaths.clear(); - fieldIndices.clear(); - labels.clear(); - types.clear(); - keys.clear(); - addKeys.clear(); - addButtons.clear(); - removeButtons.clear(); - genericTagPropertiesEditPanel.removeAll(); - genericTagPropertiesEditPanel.setSize(0, 0); - } - - public void setEditMode(boolean edit, Tag tag) { - if (tag == null) { - tag = this.tag; - } - - this.tag = tag; - this.editedTag = Helper.deepCopy(tag); - generateEditControls(editedTag, !edit); - - if (edit) { - remove(genericTagPropertiesEditorPaneScrollPanel); - add(genericTagPropertiesEditPanelScrollPanel, BorderLayout.CENTER); - } else { - genericTagPropertiesEditPanel.removeAll(); - genericTagPropertiesEditPanel.setSize(0, 0); - remove(genericTagPropertiesEditPanelScrollPanel); - add(genericTagPropertiesEditorPaneScrollPanel, BorderLayout.CENTER); - setTagText(this.tag); - } - revalidate(); - repaint(); - } - - private void setTagText(Tag tag) { - clear(); - generateEditControls(tag, true); - String val = ""; - for (String key : keys) { - GenericTagEditor ed = editors.get(key); - if (((Component) ed).isVisible()) { - val += key + " : " + ed.getReadOnlyValue() + "
"; - } - } - //HTML for colors: - val = "" + val + ""; - genericTagPropertiesEditorPane.setContentType("text/html"); - genericTagPropertiesEditorPane.setText(val); - genericTagPropertiesEditorPane.setCaretPosition(0); - hdr.setText(tag.toString()); - } - - private void generateEditControls(Tag tag, boolean readonly) { - clear(); - generateEditControlsRecursive(tag, "", new ArrayList<>(), new ArrayList<>(), readonly); - change(null); - } - - private void relayout(int propCount) { - //Lay out the panel. - SpringUtilities.makeCompactGrid(genericTagPropertiesEditPanel, - propCount, 3, //rows, cols - 6, 6, //initX, initY - 6, 6); //xPad, yPad - revalidate(); - repaint(); - } - - private int generateEditControlsRecursive(final Object obj, String parent, List parentFields, List parentIndices, boolean readonly) { - if (obj == null) { - return 0; - } - Field[] fields = obj.getClass().getDeclaredFields(); - int propCount = 0; - for (final Field field : fields) { - try { - if (Modifier.isStatic(field.getModifiers())) { - continue; - } - field.setAccessible(true); - String name = parent + field.getName(); - final Object value = field.get(obj); - if (List.class.isAssignableFrom(field.getType())) { - if (value != null) { - int i = 0; - for (Object obj1 : (Iterable) value) { - final String subname = name + "[" + i + "]"; - propCount += addEditor(subname, obj, field, i, obj1.getClass(), obj1, parentFields, parentIndices, readonly); - final int fi = i; - i++; - JButton removeButton = new JButton(View.getIcon("close16")); - removeButton.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - removeItem(obj, field, fi); - } - }); - removeButtons.put(subname, removeButton); - } - } - } else if (field.getType().isArray()) { - if (value != null) { - for (int i = 0; i < Array.getLength(value); i++) { - Object item = Array.get(value, i); - String subname = name + "[" + i + "]"; - propCount += addEditor(subname, obj, field, i, item.getClass(), item, parentFields, parentIndices, readonly); - final int fi = i; - JButton removeButton = new JButton(View.getIcon("close16")); - removeButton.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - removeItem(obj, field, fi); - } - }); - removeButtons.put(subname, removeButton); - } - } - } else { - propCount += addEditor(name, obj, field, 0, field.getType(), value, parentFields, parentIndices, readonly); - } - if (ReflectionTools.needsIndex(field) && !readonly && !field.getName().equals("clipActionRecords")) { //No clip actions, sorry - JButton addButton = new JButton(View.getIcon("add16")); - addButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - addItem(obj, field); - } - }); - name += "[]"; - - List parList = new ArrayList<>(parentFields); - parList.add(field); - fieldPaths.put(name, parList); - - List parIndices = new ArrayList<>(parentIndices); - parIndices.add(0); - fieldIndices.put(name, parIndices); - - addRow(name, addButton, field); - addKeys.add(name); - addButtons.put(name, addButton); - } - } catch (IllegalArgumentException | IllegalAccessException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - return propCount; - } - - private void removeItem(Object obj, Field field, int index) { - final JScrollBar sb = genericTagPropertiesEditPanelScrollPanel.getVerticalScrollBar(); - final int val = sb.getValue(); //save scroll top - SWFType swfType = field.getAnnotation(SWFType.class); - if (swfType != null && !swfType.countField().isEmpty()) { //Fields with same countField must be removed from too - Field fields[] = obj.getClass().getDeclaredFields(); - for (int f = 0; f < fields.length; f++) { - SWFType fieldSwfType = fields[f].getAnnotation(SWFType.class); - if (fieldSwfType != null && fieldSwfType.countField().equals(swfType.countField())) { - ReflectionTools.removeFromField(obj, fields[f], index); - } - } - try { - //If countField exists, decrement, otherwise do nothing - Field countField = obj.getClass().getDeclaredField(swfType.countField()); - int cnt = countField.getInt(obj); - cnt--; - countField.setInt(obj, cnt); - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) { - //ignored - } - } else { - ReflectionTools.removeFromField(obj, field, index); - } - - generateEditControls(editedTag, false); - - //Restore scroll top after some time. TODO: Handle this better. I don't know how :-(. - new Thread() { - - @Override - public void run() { - try { - Thread.sleep(500); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - - View.execInEventDispatch(() -> { - genericTagPropertiesEditPanelScrollPanel.getVerticalScrollBar().setValue(val); - }); - } - - }.start(); - revalidate(); - repaint(); - } - - private void addItem(Object obj, Field field) { - final JScrollBar sb = genericTagPropertiesEditPanelScrollPanel.getVerticalScrollBar(); - final int val = sb.getValue(); //save scroll top - SWFType swfType = field.getAnnotation(SWFType.class); - if (swfType != null && !swfType.countField().isEmpty()) { //Fields with same countField must be enlarged too - Field fields[] = obj.getClass().getDeclaredFields(); - for (int f = 0; f < fields.length; f++) { - SWFType fieldSwfType = fields[f].getAnnotation(SWFType.class); - if (fieldSwfType != null && fieldSwfType.countField().equals(swfType.countField())) { - ReflectionTools.addToField(obj, fields[f], ReflectionTools.getFieldSubSize(obj, fields[f]), true); - } - } - try { - //If countField exists, increment, otherwise do nothing - Field countField = obj.getClass().getDeclaredField(swfType.countField()); - int cnt = countField.getInt(obj); - cnt++; - countField.setInt(obj, cnt); - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) { - //ignored - } - } else { - ReflectionTools.addToField(obj, field, ReflectionTools.getFieldSubSize(obj, field), true); - } - generateEditControls(editedTag, false); - - //Restore scroll top after some time. TODO: Handle this better. I don't know how :-(. - new Thread() { - - @Override - public void run() { - try { - Thread.sleep(500); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - - View.execInEventDispatch(() -> { - genericTagPropertiesEditPanelScrollPanel.getVerticalScrollBar().setValue(val); - }); - } - - }.start(); - revalidate(); - repaint(); - } - - private int addEditor(String name, Object obj, Field field, int index, Class type, Object value, List parentList, List parentIndices, boolean readonly) throws IllegalArgumentException, IllegalAccessException { - Calculated calculated = field.getAnnotation(Calculated.class); - if (calculated != null) { - return 0; - } - List parList = new ArrayList<>(parentList); - parList.add(field); - - List parIndices = new ArrayList<>(parentIndices); - parIndices.add(index); - Internal inter = field.getAnnotation(Internal.class); - if (inter != null) { - return 0; - } - SWFType swfType = field.getAnnotation(SWFType.class); - Multiline multiline = field.getAnnotation(Multiline.class); - Component editor; - if (type.equals(int.class) || type.equals(Integer.class) - || type.equals(short.class) || type.equals(Short.class) - || type.equals(long.class) || type.equals(Long.class) - || type.equals(double.class) || type.equals(Double.class) - || type.equals(float.class) || type.equals(Float.class)) { - editor = new NumberEditor(name, obj, field, index, type, swfType); - } else if (type.equals(boolean.class) || type.equals(Boolean.class)) { - editor = new BooleanEditor(name, obj, field, index, type); - } else if (type.equals(String.class)) { - editor = new StringEditor(name, obj, field, index, type, multiline != null); - } else if (type.equals(RGB.class) || type.equals(RGBA.class) || type.equals(ARGB.class)) { - editor = new ColorEditor(name, obj, field, index, type); - } else { - if (value == null) { - if (readonly) { - return 0; - } - Optional opt = field.getAnnotation(Optional.class); - if (opt == null) { - try { - value = ReflectionTools.newInstanceOf(field.getType()); - field.set(obj, value); - } catch (InstantiationException | IllegalAccessException ex) { - logger.log(Level.SEVERE, null, ex); - return 0; - } - } else { - return 0; - } - } - return generateEditControlsRecursive(value, name + ".", parList, parIndices, readonly); - } - if (editor instanceof GenericTagEditor) { - GenericTagEditor ce = (GenericTagEditor) editor; - ce.addChangeListener(this); - editors.put(name, ce); - fieldPaths.put(name, parList); - fieldIndices.put(name, parIndices); - addRow(name, editor, field); - } - return 1; - } - - private void addRow(String name, Component editor, Field field) { - JLabel label = new JLabel(name + ":", JLabel.TRAILING); - label.setVerticalAlignment(JLabel.TOP); - genericTagPropertiesEditPanel.add(label); - label.setLabelFor(editor); - labels.put(name, label); - genericTagPropertiesEditPanel.add(editor); - JLabel typeLabel = new JLabel(swfTypeToString(field.getAnnotation(SWFType.class)), JLabel.TRAILING); - typeLabel.setVerticalAlignment(JLabel.TOP); - genericTagPropertiesEditPanel.add(typeLabel); - types.put(name, typeLabel); - keys.add(name); - } - - public String swfTypeToString(SWFType swfType) { - if (swfType == null) { - return null; - } - String result = swfType.value().toString(); - if (swfType.count() > 0) { - result += "[" + swfType.count(); - if (swfType.countAdd() > 0) { - result += " + " + swfType.countAdd(); - } - result += "]"; - } else if (!swfType.countField().isEmpty()) { - result += "[" + swfType.countField(); - if (swfType.countAdd() > 0) { - result += " + " + swfType.countAdd(); - } - result += "]"; - } - return result; - } - - private void assignTag(Tag t, Tag assigned) { - if (t.getClass() != assigned.getClass()) { - return; - } - for (Field f : t.getClass().getDeclaredFields()) { - if ((f.getModifiers() & Modifier.FINAL) == Modifier.FINAL) { - continue; - } - if ((f.getModifiers() & Modifier.STATIC) == Modifier.STATIC) { - continue; - } - try { - f.set(t, f.get(assigned)); - } catch (IllegalArgumentException | IllegalAccessException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - } - - public void save() { - for (Object component : genericTagPropertiesEditPanel.getComponents()) { - if (component instanceof GenericTagEditor) { - ((GenericTagEditor) component).save(); - } - } - SWF swf = tag.getSwf(); - assignTag(tag, editedTag); - tag.setModified(true); - tag.setSwf(swf); - setTagText(tag); - } - - public Tag getTag() { - return tag; - } - - @Override - public void change(GenericTagEditor ed) { - for (String key : editors.keySet()) { - GenericTagEditor dependentEditor = editors.get(key); - Component dependentLabel = labels.get(key); - Component dependentTypeLabel = types.get(key); - List path = fieldPaths.get(key); - List indices = fieldIndices.get(key); - String p = ""; - boolean conditionMet = true; - for (int i = 0; i < path.size(); i++) { - Field f = path.get(i); - int index = indices.get(i); - String par = p; - if (!p.isEmpty()) { - p += "."; - } - p += f.getName(); - if (ReflectionTools.needsIndex(f)) { - p += "[" + index + "]"; - } - Conditional cond = f.getAnnotation(Conditional.class); - if (cond != null) { - ConditionEvaluator ev = new ConditionEvaluator(cond); - - try { - Set fieldNames = ev.getFields(); - Map fields = new HashMap<>(); - for (String fld : fieldNames) { - String ckey = ""; - if (!par.isEmpty()) { - ckey = par + "."; - } - ckey += fld; - if (editors.containsKey(ckey)) { - GenericTagEditor editor = editors.get(ckey); - Object val = editor.getChangedValue(); - fields.put(fld, true); - if (val instanceof Boolean) { - fields.put(fld, (Boolean) val); - } - } - } - boolean ok = ev.eval(fields); - if (conditionMet) { - conditionMet = ok; - } - ((Component) dependentEditor).setVisible(conditionMet); - dependentLabel.setVisible(conditionMet); - dependentTypeLabel.setVisible(conditionMet); - } catch (AnnotationParseException ex) { - logger.log(Level.SEVERE, "Invalid condition", ex); - } - } - if (!conditionMet) { - break; - } - } - } - genericTagPropertiesEditPanel.removeAll(); - genericTagPropertiesEditPanel.setSize(0, 0); - int propCount = 0; - for (String key : keys) { - - Component dependentEditor; - if (addKeys.contains(key)) { - dependentEditor = addButtons.get(key); - } else if (removeButtons.containsKey(key)) { //It's array/list, add remove button - JPanel editRemPanel = new JPanel(new BorderLayout()); - editRemPanel.add((Component) editors.get(key), BorderLayout.CENTER); - editRemPanel.add(removeButtons.get(key), BorderLayout.EAST); - dependentEditor = editRemPanel; - } else { - dependentEditor = (Component) editors.get(key); - } - Component dependentLabel = labels.get(key); - Component dependentTypeLabel = types.get(key); - if (dependentEditor.isVisible()) { - genericTagPropertiesEditPanel.add(dependentLabel); - genericTagPropertiesEditPanel.add(((Component) dependentEditor)); - genericTagPropertiesEditPanel.add(dependentTypeLabel); - propCount++; - } - } - /*genericTagPropertiesEditPanel.add(new JPanel()); - genericTagPropertiesEditPanel.add(new JPanel()); - genericTagPropertiesEditPanel.add(new JPanel());*/ - relayout(propCount /*+ 1*/); - } -} +/* + * Copyright (C) 2010-2015 JPEXS + * + * 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; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.gui.generictageditors.BooleanEditor; +import com.jpexs.decompiler.flash.gui.generictageditors.ChangeListener; +import com.jpexs.decompiler.flash.gui.generictageditors.ColorEditor; +import com.jpexs.decompiler.flash.gui.generictageditors.GenericTagEditor; +import com.jpexs.decompiler.flash.gui.generictageditors.NumberEditor; +import com.jpexs.decompiler.flash.gui.generictageditors.StringEditor; +import com.jpexs.decompiler.flash.gui.helpers.SpringUtilities; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.types.ARGB; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.annotations.Calculated; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.Multiline; +import com.jpexs.decompiler.flash.types.annotations.Optional; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.parser.AnnotationParseException; +import com.jpexs.decompiler.flash.types.annotations.parser.ConditionEvaluator; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.ReflectionTools; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JButton; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.SpringLayout; + +/** + * Old Generic Tag editor + * + * @author JPEXS + */ +public class GenericTagPanel extends JPanel implements ChangeListener { + + private static final Logger logger = Logger.getLogger(GenericTagPanel.class.getName()); + + private final JEditorPane genericTagPropertiesEditorPane; + + private final JPanel genericTagPropertiesEditPanel; + + private final JScrollPane genericTagPropertiesEditorPaneScrollPanel; + + private final JScrollPane genericTagPropertiesEditPanelScrollPanel; + + private Tag tag; + + private Tag editedTag; + + private List keys = new ArrayList<>(); + + private Map editors = new HashMap<>(); + + private Map labels = new HashMap<>(); + + private Map types = new HashMap<>(); + + private Map> fieldPaths = new HashMap<>(); + + private Map> fieldIndices = new HashMap<>(); + + private HeaderLabel hdr; + + private Set addKeys = new HashSet<>(); + + private Map addButtons = new HashMap<>(); + + private Map removeButtons = new HashMap<>(); + + public GenericTagPanel() { + super(new BorderLayout()); + + hdr = new HeaderLabel(""); + add(hdr, BorderLayout.NORTH); + genericTagPropertiesEditorPane = new JEditorPane() { + @Override + public boolean getScrollableTracksViewportWidth() { + return true; + } + }; + genericTagPropertiesEditorPane.setEditable(false); + genericTagPropertiesEditorPaneScrollPanel = new JScrollPane(genericTagPropertiesEditorPane); + add(genericTagPropertiesEditorPaneScrollPanel, BorderLayout.CENTER); + + genericTagPropertiesEditPanel = new JPanel(); + genericTagPropertiesEditPanel.setLayout(new SpringLayout()); + JPanel edPanel = new JPanel(new BorderLayout()); + edPanel.add(genericTagPropertiesEditPanel, BorderLayout.NORTH); + genericTagPropertiesEditPanelScrollPanel = new JScrollPane(edPanel); + } + + public void clear() { + editors.clear(); + fieldPaths.clear(); + fieldIndices.clear(); + labels.clear(); + types.clear(); + keys.clear(); + addKeys.clear(); + addButtons.clear(); + removeButtons.clear(); + genericTagPropertiesEditPanel.removeAll(); + genericTagPropertiesEditPanel.setSize(0, 0); + } + + public void setEditMode(boolean edit, Tag tag) { + if (tag == null) { + tag = this.tag; + } + + this.tag = tag; + this.editedTag = Helper.deepCopy(tag); + generateEditControls(editedTag, !edit); + + if (edit) { + remove(genericTagPropertiesEditorPaneScrollPanel); + add(genericTagPropertiesEditPanelScrollPanel, BorderLayout.CENTER); + } else { + genericTagPropertiesEditPanel.removeAll(); + genericTagPropertiesEditPanel.setSize(0, 0); + remove(genericTagPropertiesEditPanelScrollPanel); + add(genericTagPropertiesEditorPaneScrollPanel, BorderLayout.CENTER); + setTagText(this.tag); + } + revalidate(); + repaint(); + } + + private void setTagText(Tag tag) { + clear(); + generateEditControls(tag, true); + String val = ""; + for (String key : keys) { + GenericTagEditor ed = editors.get(key); + if (((Component) ed).isVisible()) { + val += key + " : " + ed.getReadOnlyValue() + "
"; + } + } + //HTML for colors: + val = "" + val + ""; + genericTagPropertiesEditorPane.setContentType("text/html"); + genericTagPropertiesEditorPane.setText(val); + genericTagPropertiesEditorPane.setCaretPosition(0); + hdr.setText(tag.toString()); + } + + private void generateEditControls(Tag tag, boolean readonly) { + clear(); + generateEditControlsRecursive(tag, "", new ArrayList<>(), new ArrayList<>(), readonly); + change(null); + } + + private void relayout(int propCount) { + //Lay out the panel. + SpringUtilities.makeCompactGrid(genericTagPropertiesEditPanel, + propCount, 3, //rows, cols + 6, 6, //initX, initY + 6, 6); //xPad, yPad + revalidate(); + repaint(); + } + + private int generateEditControlsRecursive(final Object obj, String parent, List parentFields, List parentIndices, boolean readonly) { + if (obj == null) { + return 0; + } + Field[] fields = obj.getClass().getDeclaredFields(); + int propCount = 0; + for (final Field field : fields) { + try { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + field.setAccessible(true); + String name = parent + field.getName(); + final Object value = field.get(obj); + if (List.class.isAssignableFrom(field.getType())) { + if (value != null) { + int i = 0; + for (Object obj1 : (Iterable) value) { + final String subname = name + "[" + i + "]"; + propCount += addEditor(subname, obj, field, i, obj1.getClass(), obj1, parentFields, parentIndices, readonly); + final int fi = i; + i++; + JButton removeButton = new JButton(View.getIcon("close16")); + removeButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + removeItem(obj, field, fi); + } + }); + removeButtons.put(subname, removeButton); + } + } + } else if (field.getType().isArray()) { + if (value != null) { + for (int i = 0; i < Array.getLength(value); i++) { + Object item = Array.get(value, i); + String subname = name + "[" + i + "]"; + propCount += addEditor(subname, obj, field, i, item.getClass(), item, parentFields, parentIndices, readonly); + final int fi = i; + JButton removeButton = new JButton(View.getIcon("close16")); + removeButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + removeItem(obj, field, fi); + } + }); + removeButtons.put(subname, removeButton); + } + } + } else { + propCount += addEditor(name, obj, field, 0, field.getType(), value, parentFields, parentIndices, readonly); + } + if (ReflectionTools.needsIndex(field) && !readonly && !field.getName().equals("clipActionRecords")) { //No clip actions, sorry + JButton addButton = new JButton(View.getIcon("add16")); + addButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + addItem(obj, field); + } + }); + name += "[]"; + + List parList = new ArrayList<>(parentFields); + parList.add(field); + fieldPaths.put(name, parList); + + List parIndices = new ArrayList<>(parentIndices); + parIndices.add(0); + fieldIndices.put(name, parIndices); + + addRow(name, addButton, field); + addKeys.add(name); + addButtons.put(name, addButton); + } + } catch (IllegalArgumentException | IllegalAccessException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + return propCount; + } + + private void removeItem(Object obj, Field field, int index) { + final JScrollBar sb = genericTagPropertiesEditPanelScrollPanel.getVerticalScrollBar(); + final int val = sb.getValue(); //save scroll top + SWFType swfType = field.getAnnotation(SWFType.class); + if (swfType != null && !swfType.countField().isEmpty()) { //Fields with same countField must be removed from too + Field fields[] = obj.getClass().getDeclaredFields(); + for (int f = 0; f < fields.length; f++) { + SWFType fieldSwfType = fields[f].getAnnotation(SWFType.class); + if (fieldSwfType != null && fieldSwfType.countField().equals(swfType.countField())) { + ReflectionTools.removeFromField(obj, fields[f], index); + } + } + try { + //If countField exists, decrement, otherwise do nothing + Field countField = obj.getClass().getDeclaredField(swfType.countField()); + int cnt = countField.getInt(obj); + cnt--; + countField.setInt(obj, cnt); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) { + //ignored + } + } else { + ReflectionTools.removeFromField(obj, field, index); + } + + generateEditControls(editedTag, false); + + //Restore scroll top after some time. TODO: Handle this better. I don't know how :-(. + new Thread() { + + @Override + public void run() { + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + + View.execInEventDispatch(() -> { + genericTagPropertiesEditPanelScrollPanel.getVerticalScrollBar().setValue(val); + }); + } + + }.start(); + revalidate(); + repaint(); + } + + private void addItem(Object obj, Field field) { + final JScrollBar sb = genericTagPropertiesEditPanelScrollPanel.getVerticalScrollBar(); + final int val = sb.getValue(); //save scroll top + SWFType swfType = field.getAnnotation(SWFType.class); + if (swfType != null && !swfType.countField().isEmpty()) { //Fields with same countField must be enlarged too + Field fields[] = obj.getClass().getDeclaredFields(); + for (int f = 0; f < fields.length; f++) { + SWFType fieldSwfType = fields[f].getAnnotation(SWFType.class); + if (fieldSwfType != null && fieldSwfType.countField().equals(swfType.countField())) { + ReflectionTools.addToField(obj, fields[f], ReflectionTools.getFieldSubSize(obj, fields[f]), true, null); + } + } + try { + //If countField exists, increment, otherwise do nothing + Field countField = obj.getClass().getDeclaredField(swfType.countField()); + int cnt = countField.getInt(obj); + cnt++; + countField.setInt(obj, cnt); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) { + //ignored + } + } else { + ReflectionTools.addToField(obj, field, ReflectionTools.getFieldSubSize(obj, field), true, null); + } + generateEditControls(editedTag, false); + + //Restore scroll top after some time. TODO: Handle this better. I don't know how :-(. + new Thread() { + + @Override + public void run() { + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + + View.execInEventDispatch(() -> { + genericTagPropertiesEditPanelScrollPanel.getVerticalScrollBar().setValue(val); + }); + } + + }.start(); + revalidate(); + repaint(); + } + + private int addEditor(String name, Object obj, Field field, int index, Class type, Object value, List parentList, List parentIndices, boolean readonly) throws IllegalArgumentException, IllegalAccessException { + Calculated calculated = field.getAnnotation(Calculated.class); + if (calculated != null) { + return 0; + } + List parList = new ArrayList<>(parentList); + parList.add(field); + + List parIndices = new ArrayList<>(parentIndices); + parIndices.add(index); + Internal inter = field.getAnnotation(Internal.class); + if (inter != null) { + return 0; + } + SWFType swfType = field.getAnnotation(SWFType.class); + Multiline multiline = field.getAnnotation(Multiline.class); + Component editor; + if (type.equals(int.class) || type.equals(Integer.class) + || type.equals(short.class) || type.equals(Short.class) + || type.equals(long.class) || type.equals(Long.class) + || type.equals(double.class) || type.equals(Double.class) + || type.equals(float.class) || type.equals(Float.class)) { + editor = new NumberEditor(name, obj, field, index, type, swfType); + } else if (type.equals(boolean.class) || type.equals(Boolean.class)) { + editor = new BooleanEditor(name, obj, field, index, type); + } else if (type.equals(String.class)) { + editor = new StringEditor(name, obj, field, index, type, multiline != null); + } else if (type.equals(RGB.class) || type.equals(RGBA.class) || type.equals(ARGB.class)) { + editor = new ColorEditor(name, obj, field, index, type); + } else { + if (value == null) { + if (readonly) { + return 0; + } + Optional opt = field.getAnnotation(Optional.class); + if (opt == null) { + try { + value = ReflectionTools.newInstanceOf(field.getType()); + field.set(obj, value); + } catch (InstantiationException | IllegalAccessException ex) { + logger.log(Level.SEVERE, null, ex); + return 0; + } + } else { + return 0; + } + } + return generateEditControlsRecursive(value, name + ".", parList, parIndices, readonly); + } + if (editor instanceof GenericTagEditor) { + GenericTagEditor ce = (GenericTagEditor) editor; + ce.addChangeListener(this); + editors.put(name, ce); + fieldPaths.put(name, parList); + fieldIndices.put(name, parIndices); + addRow(name, editor, field); + } + return 1; + } + + private void addRow(String name, Component editor, Field field) { + JLabel label = new JLabel(name + ":", JLabel.TRAILING); + label.setVerticalAlignment(JLabel.TOP); + genericTagPropertiesEditPanel.add(label); + label.setLabelFor(editor); + labels.put(name, label); + genericTagPropertiesEditPanel.add(editor); + JLabel typeLabel = new JLabel(swfTypeToString(field.getAnnotation(SWFType.class)), JLabel.TRAILING); + typeLabel.setVerticalAlignment(JLabel.TOP); + genericTagPropertiesEditPanel.add(typeLabel); + types.put(name, typeLabel); + keys.add(name); + } + + public String swfTypeToString(SWFType swfType) { + if (swfType == null) { + return null; + } + String result = swfType.value().toString(); + if (swfType.count() > 0) { + result += "[" + swfType.count(); + if (swfType.countAdd() > 0) { + result += " + " + swfType.countAdd(); + } + result += "]"; + } else if (!swfType.countField().isEmpty()) { + result += "[" + swfType.countField(); + if (swfType.countAdd() > 0) { + result += " + " + swfType.countAdd(); + } + result += "]"; + } + return result; + } + + private void assignTag(Tag t, Tag assigned) { + if (t.getClass() != assigned.getClass()) { + return; + } + for (Field f : t.getClass().getDeclaredFields()) { + if ((f.getModifiers() & Modifier.FINAL) == Modifier.FINAL) { + continue; + } + if ((f.getModifiers() & Modifier.STATIC) == Modifier.STATIC) { + continue; + } + try { + f.set(t, f.get(assigned)); + } catch (IllegalArgumentException | IllegalAccessException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + } + + public void save() { + for (Object component : genericTagPropertiesEditPanel.getComponents()) { + if (component instanceof GenericTagEditor) { + ((GenericTagEditor) component).save(); + } + } + SWF swf = tag.getSwf(); + assignTag(tag, editedTag); + tag.setModified(true); + tag.setSwf(swf); + setTagText(tag); + } + + public Tag getTag() { + return tag; + } + + @Override + public void change(GenericTagEditor ed) { + for (String key : editors.keySet()) { + GenericTagEditor dependentEditor = editors.get(key); + Component dependentLabel = labels.get(key); + Component dependentTypeLabel = types.get(key); + List path = fieldPaths.get(key); + List indices = fieldIndices.get(key); + String p = ""; + boolean conditionMet = true; + for (int i = 0; i < path.size(); i++) { + Field f = path.get(i); + int index = indices.get(i); + String par = p; + if (!p.isEmpty()) { + p += "."; + } + p += f.getName(); + if (ReflectionTools.needsIndex(f)) { + p += "[" + index + "]"; + } + Conditional cond = f.getAnnotation(Conditional.class); + if (cond != null) { + ConditionEvaluator ev = new ConditionEvaluator(cond); + + try { + Set fieldNames = ev.getFields(); + Map fields = new HashMap<>(); + for (String fld : fieldNames) { + String ckey = ""; + if (!par.isEmpty()) { + ckey = par + "."; + } + ckey += fld; + if (editors.containsKey(ckey)) { + GenericTagEditor editor = editors.get(ckey); + Object val = editor.getChangedValue(); + fields.put(fld, true); + if (val instanceof Boolean) { + fields.put(fld, (Boolean) val); + } + } + } + boolean ok = ev.eval(fields); + if (conditionMet) { + conditionMet = ok; + } + ((Component) dependentEditor).setVisible(conditionMet); + dependentLabel.setVisible(conditionMet); + dependentTypeLabel.setVisible(conditionMet); + } catch (AnnotationParseException ex) { + logger.log(Level.SEVERE, "Invalid condition", ex); + } + } + if (!conditionMet) { + break; + } + } + } + genericTagPropertiesEditPanel.removeAll(); + genericTagPropertiesEditPanel.setSize(0, 0); + int propCount = 0; + for (String key : keys) { + + Component dependentEditor; + if (addKeys.contains(key)) { + dependentEditor = addButtons.get(key); + } else if (removeButtons.containsKey(key)) { //It's array/list, add remove button + JPanel editRemPanel = new JPanel(new BorderLayout()); + editRemPanel.add((Component) editors.get(key), BorderLayout.CENTER); + editRemPanel.add(removeButtons.get(key), BorderLayout.EAST); + dependentEditor = editRemPanel; + } else { + dependentEditor = (Component) editors.get(key); + } + Component dependentLabel = labels.get(key); + Component dependentTypeLabel = types.get(key); + if (dependentEditor.isVisible()) { + genericTagPropertiesEditPanel.add(dependentLabel); + genericTagPropertiesEditPanel.add(((Component) dependentEditor)); + genericTagPropertiesEditPanel.add(dependentTypeLabel); + propCount++; + } + } + /*genericTagPropertiesEditPanel.add(new JPanel()); + genericTagPropertiesEditPanel.add(new JPanel()); + genericTagPropertiesEditPanel.add(new JPanel());*/ + relayout(propCount /*+ 1*/); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java b/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java index d6c21eae3..705a380f5 100644 --- a/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java @@ -23,6 +23,7 @@ import com.jpexs.decompiler.flash.gui.generictageditors.GenericTagEditor; import com.jpexs.decompiler.flash.gui.generictageditors.NumberEditor; import com.jpexs.decompiler.flash.gui.generictageditors.StringEditor; import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ASMSource; import com.jpexs.decompiler.flash.types.ARGB; import com.jpexs.decompiler.flash.types.BasicType; import com.jpexs.decompiler.flash.types.RGB; @@ -36,6 +37,7 @@ import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.annotations.Table; import com.jpexs.decompiler.flash.types.annotations.parser.AnnotationParseException; import com.jpexs.decompiler.flash.types.annotations.parser.ConditionEvaluator; +import com.jpexs.helpers.ConcreteClasses; import com.jpexs.helpers.ReflectionTools; import java.awt.BorderLayout; import java.awt.Color; @@ -62,6 +64,7 @@ import java.util.logging.Logger; import javax.swing.AbstractCellEditor; import javax.swing.JComponent; import javax.swing.JLabel; +import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -271,7 +274,7 @@ public class GenericTagTreePanel extends GenericTagPanel { if (swfArray != null) { itemStr = swfArray.value(); } - if (!fnode.fieldSet.itemName.isEmpty()) { + if (fnode.fieldSet.itemName != null && !fnode.fieldSet.itemName.isEmpty()) { itemStr = fnode.fieldSet.itemName; } if (itemStr.isEmpty()) { @@ -285,70 +288,116 @@ public class GenericTagTreePanel extends GenericTagPanel { } JPopupMenu p = new JPopupMenu(); JMenuItem mi; - mi = new JMenuItem(AppStrings.translate("generictag.array.insertbeginning").replace("%item%", itemStr)); - mi.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), 0); - } - }); - if (!canAdd) { - mi.setEnabled(false); - } - p.add(mi); - - if (fnode.index > -1) { - mi = new JMenuItem(AppStrings.translate("generictag.array.insertbefore").replace("%item%", itemStr)); - mi.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index); - } - }); - if (!canAdd) { - mi.setEnabled(false); - } - p.add(mi); - + Class subtype = ReflectionTools.getFieldSubType(fnode.obj, fnode.fieldSet.get(FIELD_INDEX)); + if (!canAdd && subtype.isAnnotationPresent(ConcreteClasses.class)) { + Class[] availableClasses = subtype.getAnnotation(ConcreteClasses.class).value(); + JMenu mBegin = new JMenu(AppStrings.translate("generictag.array.insertbeginning").replace("%item%", itemStr)); + p.add(mBegin); + JMenu mBefore = new JMenu(AppStrings.translate("generictag.array.insertbefore").replace("%item%", itemStr)); + p.add(mBefore); mi = new JMenuItem(AppStrings.translate("generictag.array.remove").replace("%item%", itemStr)); - mi.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - removeItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index); - } + mi.addActionListener((ActionEvent e1) -> { + removeItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index); }); p.add(mi); + JMenu mAfter = new JMenu(AppStrings.translate("generictag.array.insertafter").replace("%item%", itemStr)); + p.add(mAfter); - mi = new JMenuItem(AppStrings.translate("generictag.array.insertafter").replace("%item%", itemStr)); + JMenu mEnd = new JMenu(AppStrings.translate("generictag.array.insertend").replace("%item%", itemStr)); + p.add(mEnd); + + for (Class c : availableClasses) { + mi = new JMenuItem(c.getSimpleName()); + mi.addActionListener((ActionEvent e1) -> { + addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), 0, c); + }); + mBegin.add(mi); + + mi = new JMenuItem(c.getSimpleName()); + mi.addActionListener((ActionEvent e1) -> { + addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index, c); + }); + mBefore.add(mi); + + mi = new JMenuItem(c.getSimpleName()); + mi.addActionListener((ActionEvent e1) -> { + addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index + 1, c); + }); + mAfter.add(mi); + + mi = new JMenuItem(c.getSimpleName()); + mi.addActionListener((ActionEvent e1) -> { + addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), ReflectionTools.getFieldSubSize(fnode.obj, fnode.fieldSet.get(FIELD_INDEX)), c); + }); + mEnd.add(mi); + } + } else { + + mi = new JMenuItem(AppStrings.translate("generictag.array.insertbeginning").replace("%item%", itemStr)); mi.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index + 1); + addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), 0, null); } }); if (!canAdd) { mi.setEnabled(false); } p.add(mi); - } - mi = new JMenuItem(AppStrings.translate("generictag.array.insertend").replace("%item%", itemStr)); - mi.addActionListener(new ActionListener() { + if (fnode.index > -1) { + mi = new JMenuItem(AppStrings.translate("generictag.array.insertbefore").replace("%item%", itemStr)); + mi.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), ReflectionTools.getFieldSubSize(fnode.obj, fnode.fieldSet.get(FIELD_INDEX))); + @Override + public void actionPerformed(ActionEvent e) { + addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index, null); + } + }); + if (!canAdd) { + mi.setEnabled(false); + } + p.add(mi); + + mi = new JMenuItem(AppStrings.translate("generictag.array.remove").replace("%item%", itemStr)); + mi.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + removeItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index); + } + }); + p.add(mi); + + mi = new JMenuItem(AppStrings.translate("generictag.array.insertafter").replace("%item%", itemStr)); + mi.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), fnode.index + 1, null); + } + }); + if (!canAdd) { + mi.setEnabled(false); + } + p.add(mi); } - }); - if (!canAdd) { - mi.setEnabled(false); + + mi = new JMenuItem(AppStrings.translate("generictag.array.insertend").replace("%item%", itemStr)); + mi.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + addItem(fnode.obj, fnode.fieldSet.get(FIELD_INDEX), ReflectionTools.getFieldSubSize(fnode.obj, fnode.fieldSet.get(FIELD_INDEX)), null); + } + }); + if (!canAdd) { + mi.setEnabled(false); + } + p.add(mi); + //} } - p.add(mi); - //} p.show(tree, e.getX(), e.getY()); } } @@ -498,7 +547,21 @@ public class GenericTagTreePanel extends GenericTagPanel { if (swfArray != null) { name = swfArray.value(); } - return (index > -1 ? name + "[" + index + "]" : fieldSet.get(fieldIndex).getName()); + + Object val = null; + try { + if (index > -1) { + val = ReflectionTools.getValue(obj, fieldSet.get(fieldIndex), index); + } + } catch (IllegalArgumentException | IllegalAccessException ex) { + //ignore + } + String typeAdd = ""; + if (val != null) { + typeAdd = " : " + val.getClass().getSimpleName(); + } + + return (index > -1 ? name + "[" + index + "]" + typeAdd : fieldSet.get(fieldIndex).getName()); } public Object getValue(int fieldIndex) { @@ -953,7 +1016,7 @@ public class GenericTagTreePanel extends GenericTagPanel { return ret; } - private void addItem(Object obj, Field field, int index) { + private void addItem(Object obj, Field field, int index, Class cls) { SWFArray swfArray = field.getAnnotation(SWFArray.class); if (swfArray != null && !swfArray.countField().isEmpty()) { //Fields with same countField must be enlarged too Field fields[] = obj.getClass().getDeclaredFields(); @@ -962,7 +1025,7 @@ public class GenericTagTreePanel extends GenericTagPanel { SWFArray fieldSwfArray = fields[f].getAnnotation(SWFArray.class); if (fieldSwfArray != null && fieldSwfArray.countField().equals(swfArray.countField())) { sameFlds.add(f); - if (!ReflectionTools.canAddToField(obj, fields[f])) { + if (cls == null && !ReflectionTools.canAddToField(obj, fields[f])) { JOptionPane.showMessageDialog(this, "This field is abstract, cannot be instantiated, sorry."); //TODO!!! return; } @@ -970,7 +1033,16 @@ public class GenericTagTreePanel extends GenericTagPanel { } } for (int f : sameFlds) { - ReflectionTools.addToField(obj, fields[f], index, true); + ReflectionTools.addToField(obj, fields[f], index, true, cls); + try { + Object v = ReflectionTools.getValue(obj, fields[f], index); + if (v instanceof ASMSource) { + ASMSource asv = (ASMSource) v; + asv.setSourceTag(editedTag); + } + } catch (IllegalArgumentException | IllegalAccessException ex) { + //ignore + } } try { //If countField exists, increment, otherwise do nothing @@ -982,11 +1054,20 @@ public class GenericTagTreePanel extends GenericTagPanel { //ignored } } else { - if (!ReflectionTools.canAddToField(obj, field)) { + if (cls == null && !ReflectionTools.canAddToField(obj, field)) { JOptionPane.showMessageDialog(this, "This field is abstract, cannot be instantiated, sorry."); //TODO!!! return; } - ReflectionTools.addToField(obj, field, index, true); + ReflectionTools.addToField(obj, field, index, true, cls); + try { + Object v = ReflectionTools.getValue(obj, field, index); + if (v instanceof ASMSource) { + ASMSource asv = (ASMSource) v; + asv.setSourceTag(editedTag); + } + } catch (IllegalArgumentException | IllegalAccessException ex) { + //ignore + } } refreshTree(); }