diff --git a/CHANGELOG.md b/CHANGELOG.md index 64029cde6..c1b44b8d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ### Added - "Starting Flash content debugger" in status bar when debugging starts - Simple editor - edit parameters of items inside buttons +- Simple editor - add/remove frames in buttons ### Fixed - Resize export dialogs labels to match localized strings diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButton2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButton2Tag.java index 5b3a8c4a6..f8c50ef6e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButton2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButton2Tag.java @@ -270,7 +270,7 @@ public class DefineButton2Tag extends ButtonTag implements ASMSourceContainer { layer.filters = r.filterList; layer.matrix = r.placeMatrix; layer.characterId = r.characterId; - layer.depth = r.placeDepth; + layer.depth = r.placeDepth; if (r.placeDepth > maxDepth) { maxDepth = r.placeDepth; @@ -278,15 +278,28 @@ public class DefineButton2Tag extends ButtonTag implements ASMSourceContainer { if (r.buttonStateUp) { frameUp.layers.put(r.placeDepth, new DepthState(layer, frameUp, frameUp, false)); + frameUp.layers.get(r.placeDepth).key = true; } - if (r.buttonStateDown) { - frameDown.layers.put(r.placeDepth, new DepthState(layer, frameDown, frameDown, false)); - } + if (r.buttonStateOver) { frameOver.layers.put(r.placeDepth, new DepthState(layer, frameOver, frameOver, false)); + if (!r.buttonStateUp) { + frameOver.layers.get(r.placeDepth).key = true; + } } + + if (r.buttonStateDown) { + frameDown.layers.put(r.placeDepth, new DepthState(layer, frameDown, frameDown, false)); + if (!r.buttonStateOver) { + frameDown.layers.get(r.placeDepth).key = true; + } + } + if (r.buttonStateHitTest) { frameHit.layers.put(r.placeDepth, new DepthState(layer, frameHit, frameHit, false)); + if (!r.buttonStateDown) { + frameHit.layers.get(r.placeDepth).key = true; + } } } 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 4efa855c0..c180aad4f 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 @@ -264,14 +264,23 @@ public class DefineButtonTag extends ButtonTag implements ASMSourceContainer { if (r.buttonStateUp) { frameUp.layers.put(r.placeDepth, new DepthState(layer, frameUp, frameUp, false)); } - if (r.buttonStateDown) { - frameDown.layers.put(r.placeDepth, new DepthState(layer, frameDown, frameDown, false)); - } if (r.buttonStateOver) { frameOver.layers.put(r.placeDepth, new DepthState(layer, frameOver, frameOver, false)); + if (!r.buttonStateUp) { + frameOver.layers.get(r.placeDepth).key = true; + } + } + if (r.buttonStateDown) { + frameDown.layers.put(r.placeDepth, new DepthState(layer, frameDown, frameDown, false)); + if (!r.buttonStateOver) { + frameDown.layers.get(r.placeDepth).key = true; + } } if (r.buttonStateHitTest) { frameHit.layers.put(r.placeDepth, new DepthState(layer, frameHit, frameHit, false)); + if (!r.buttonStateDown) { + frameDown.layers.get(r.placeDepth).key = true; + } } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java index d2be8b594..447f07f7f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java @@ -35,6 +35,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -278,7 +279,7 @@ public abstract class ButtonTag extends DrawableTag implements Timelined { break; } } - + if (addIfNotExists) { BUTTONRECORD newRecord = new BUTTONRECORD(swf, this); switch (frame) { @@ -295,12 +296,52 @@ public abstract class ButtonTag extends DrawableTag implements Timelined { newRecord.buttonStateHitTest = true; break; } + newRecord.placeDepth = depth; getRecords().add(newRecord); return newRecord; } - + return null; } + + public void packRecords() { + List records = new ArrayList<>(); + for (int i = records.size() - 1; i >= 0; i--) { + BUTTONRECORD rec = records.get(i); + if (rec.isEmpty()) { + records.remove(i); + } + } + } + + public Set getEmptyFrames() { + Set ret = new LinkedHashSet<>(); + ret.add(FRAME_UP); + ret.add(FRAME_OVER); + ret.add(FRAME_DOWN); + ret.add(FRAME_HITTEST); + for (BUTTONRECORD rec : getRecords()) { + if (rec.buttonStateUp) { + ret.remove(FRAME_UP); + } + if (rec.buttonStateOver) { + ret.remove(FRAME_OVER); + } + if (rec.buttonStateDown) { + ret.remove(FRAME_DOWN); + } + if (rec.buttonStateHitTest) { + ret.remove(FRAME_HITTEST); + } + } + return ret; + } + + + public boolean isFrameEmpty(int frame) { + + return true; + } public void setRecordFromPlaceObject(int frame, PlaceObjectTypeTag placeTag) { BUTTONRECORD selectedRecord = null; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java index 3ed364823..58566c2b3 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java @@ -84,6 +84,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -144,6 +145,11 @@ public class Timeline { * Map of depth to maximum frame. */ private final Map depthMaxFrame = new HashMap<>(); + + /** + * Map of depth to maximum frame including buttons + */ + private final Map depthMaxFrameButtons = new HashMap<>(); /** * List of all ASMSources. @@ -283,6 +289,16 @@ public class Timeline { ensureInitialized(); return depthMaxFrame; } + + /** + * Gets map of depth to max frame including buttons + * + * @return Map of depth to max frame + */ + public Map getDepthMaxFrameButtons() { + ensureInitialized(); + return depthMaxFrameButtons; + } /** * Gets map of soundStream id to SoundStreamFrameRanges. @@ -325,6 +341,7 @@ public class Timeline { initialized = false; frames.clear(); depthMaxFrame.clear(); + depthMaxFrameButtons.clear(); asmSources.clear(); asmSourceContainers.clear(); actionFrames.clear(); @@ -725,14 +742,31 @@ public class Timeline { */ private synchronized void calculateMaxDepthFrames() { depthMaxFrame.clear(); + depthMaxFrameButtons.clear(); for (int d = 0; d <= maxDepth; d++) { for (int f = frames.size() - 1; f >= 0; f--) { if (frames.get(f).layers.get(d) != null) { - depthMaxFrame.put(d, f); + depthMaxFrame.put(d, f); break; } } } + + if (timelined instanceof ButtonTag) { + ButtonTag button = (ButtonTag) timelined; + Set emptyFrames = button.getEmptyFrames(); + + for (int d = 0; d <= maxDepth; d++) { + for (int f = frames.size() - 1; f >= 0; f--) { + if (frames.get(f).layers.get(d) != null) { + if (!emptyFrames.contains(f)) { + depthMaxFrameButtons.put(d, f); + break; + } + } + } + } + } } /** 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 e1b419b81..01512dcee 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 @@ -33,6 +33,7 @@ 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.Helper; import java.io.Serializable; import java.util.List; @@ -52,12 +53,14 @@ public class BUTTONRECORD implements Serializable, TreeItem, HasSwfAndTag, HasCh /** * Has blend mode? + * * @since SWF 8 */ public boolean buttonHasBlendMode; /** * Has filter list? + * * @since SWF 8 */ public boolean buttonHasFilterList; @@ -146,6 +149,7 @@ public class BUTTONRECORD implements Serializable, TreeItem, HasSwfAndTag, HasCh /** * Constructor. + * * @param swf SWF * @param tag Button tag */ @@ -154,6 +158,26 @@ public class BUTTONRECORD implements Serializable, TreeItem, HasSwfAndTag, HasCh this.tag = tag; } + public BUTTONRECORD(BUTTONRECORD source) { + this.buttonHasBlendMode = source.buttonHasBlendMode; + this.buttonHasFilterList = source.buttonHasFilterList; + this.buttonStateHitTest =source. buttonStateHitTest; + this.buttonStateDown = source.buttonStateDown; + this.buttonStateOver = source.buttonStateOver; + this.buttonStateUp = source.buttonStateUp; + this.characterId = source.characterId; + this.placeDepth = source.placeDepth; + this.placeMatrix = new MATRIX(source.placeMatrix); + this.colorTransform = source.colorTransform == null ? null : new CXFORMWITHALPHA(source.colorTransform); + this.filterList = Helper.deepCopy(source.filterList); + this.blendMode = source.blendMode; + this.swf = source.swf; + this.tag = source.tag; + this.modified = source.modified; + } + + + /** * Constructor. */ @@ -179,6 +203,7 @@ public class BUTTONRECORD implements Serializable, TreeItem, HasSwfAndTag, HasCh /** * Sets the modified flag. + * * @param value Modified flag */ public void setModified(boolean value) { @@ -211,20 +236,69 @@ public class BUTTONRECORD implements Serializable, TreeItem, HasSwfAndTag, HasCh this.characterId = characterId; } + /** + * Enables/disables specific frame + * + * @param frame Frame + * @param value Value + */ + public void setFrame(int frame, boolean value) { + switch (frame) { + case ButtonTag.FRAME_UP: + buttonStateUp = value; + break; + case ButtonTag.FRAME_OVER: + buttonStateOver = value; + break; + case ButtonTag.FRAME_DOWN: + buttonStateDown = value; + break; + case ButtonTag.FRAME_HITTEST: + buttonStateHitTest = value; + break; + } + } + + /** + * Has frame + * + * @param frame Frame + * @return True if has + */ + public boolean hasFrame(int frame) { + switch (frame) { + case ButtonTag.FRAME_UP: + return buttonStateUp; + case ButtonTag.FRAME_OVER: + return buttonStateOver; + case ButtonTag.FRAME_DOWN: + return buttonStateDown; + case ButtonTag.FRAME_HITTEST: + return buttonStateHitTest; + } + return false; + } + + /** + * Imports placeObject to this BUTTONRECORD + * + * @param placeObject Place tag + */ public void fromPlaceObject(PlaceObjectTypeTag placeObject) { placeDepth = placeObject.getDepth(); characterId = placeObject.getCharacterId(); - ColorTransform importedColorTrans = placeObject.getColorTransform(); - colorTransform = importedColorTrans == null ? null : new CXFORMWITHALPHA(placeObject.getColorTransform()); + ColorTransform importedColorTrans = placeObject.getColorTransform(); + colorTransform = importedColorTrans == null ? new CXFORMWITHALPHA() : new CXFORMWITHALPHA(placeObject.getColorTransform()); placeMatrix = placeObject.getMatrix(); blendMode = placeObject.getBlendMode(); buttonHasBlendMode = blendMode > 0; filterList = placeObject.getFilters(); buttonHasFilterList = filterList != null && !filterList.isEmpty(); } - + /** * Converts this BUTTONRECORD to a place tag. + * * @return Place tag */ public PlaceObject3Tag toPlaceObject() { @@ -260,4 +334,20 @@ public class BUTTONRECORD implements Serializable, TreeItem, HasSwfAndTag, HasCh } return placeTag; } + + public boolean isEmpty() { + if (buttonStateUp) { + return false; + } + if (buttonStateOver) { + return false; + } + if (buttonStateDown) { + return false; + } + if (buttonStateHitTest) { + return false; + } + return true; + } } diff --git a/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java b/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java index ce5efb672..9e95ba943 100644 --- a/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java +++ b/src/com/jpexs/decompiler/flash/easygui/EasySwfPanel.java @@ -298,15 +298,21 @@ public class EasySwfPanel extends JPanel { place.matrix = new MATRIX(); place.placeFlagHasMatrix = true; place.setTimelined(timelined); - if (showFrameTag == null) { - timelined.addTag(place); + + if (timelined instanceof ButtonTag) { + ButtonTag button = (ButtonTag) timelined; + button.getButtonRecordAt(fframe, newDepth, true).fromPlaceObject(place); } else { - timelined.addTag(timelined.indexOfTag(showFrameTag), place); + if (showFrameTag == null) { + timelined.addTag(place); + } else { + timelined.addTag(timelined.indexOfTag(showFrameTag), place); - if (fframe < timelined.getFrameCount() - 1) { - RemoveObject2Tag remove = new RemoveObject2Tag(timelined.getSwf()); - remove.depth = newDepth; - timelined.addTag(timelined.indexOfTag(showFrameTag) + 1, remove); + if (fframe < timelined.getFrameCount() - 1) { + RemoveObject2Tag remove = new RemoveObject2Tag(timelined.getSwf()); + remove.depth = newDepth; + timelined.addTag(timelined.indexOfTag(showFrameTag) + 1, remove); + } } } diff --git a/src/com/jpexs/decompiler/flash/easygui/TimelineBodyPanel.java b/src/com/jpexs/decompiler/flash/easygui/TimelineBodyPanel.java index d9f4f25b7..1a110aae0 100644 --- a/src/com/jpexs/decompiler/flash/easygui/TimelineBodyPanel.java +++ b/src/com/jpexs/decompiler/flash/easygui/TimelineBodyPanel.java @@ -21,6 +21,7 @@ import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.tags.RemoveObject2Tag; import com.jpexs.decompiler.flash.tags.ShowFrameTag; import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; @@ -29,6 +30,8 @@ import com.jpexs.decompiler.flash.timeline.DepthState; import com.jpexs.decompiler.flash.timeline.Frame; import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.decompiler.flash.timeline.Timelined; +import com.jpexs.decompiler.flash.types.BUTTONRECORD; +import com.jpexs.helpers.Helper; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; @@ -229,10 +232,16 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(getBackgroundColor()); - g.fillRect(0, 0, getWidth(), getHeight()); + g.fillRect(0, 0, getWidth(), getHeight()); + if (timeline == null) { return; } + + Set emptyFrames = new LinkedHashSet<>(); + if (timeline.timelined instanceof ButtonTag) { + emptyFrames = ((ButtonTag) timeline.timelined).getEmptyFrames(); + } Rectangle clip = g.getClipBounds(); int frameWidth = FRAME_WIDTH; int frameHeight = FRAME_HEIGHT; @@ -291,7 +300,7 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe } } - Map depthMaxFrames = timeline.getDepthMaxFrame(); + Map depthMaxFrames = timeline.getDepthMaxFrameButtons(); for (int d = start_d; d <= end_d; d++) { int maxFrame = depthMaxFrames.containsKey(d) ? depthMaxFrames.get(d) : -1; if (maxFrame < 0) { @@ -313,6 +322,11 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe for (int f = start_f2; f <= end_f2; f++) { DepthState fl = timeline.getFrame(f).layers.get(d); + + if (emptyFrames.contains(f)) { + fl = null; + } + boolean motionTween = fl == null ? false : fl.motionTween; DepthState flNext = f < max_f ? timeline.getFrame(f + 1).layers.get(d) : null; @@ -608,7 +622,11 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe Frame fr = timeline.getFrame(frame); DepthState ds = fr == null ? null : fr.layers.get(depth); - boolean thisEmpty = ds == null || ds.getCharacter() == null; + Set emptyFrames = new LinkedHashSet<>(); + if (timeline.timelined instanceof ButtonTag) { + emptyFrames = ((ButtonTag) timeline.timelined).getEmptyFrames(); + } + boolean thisEmpty = emptyFrames.contains(frame) || ds == null || ds.getCharacter() == null; boolean previousEmpty = true; boolean emptyDepth = true; boolean somethingBefore = false; @@ -621,7 +639,7 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe for (int f = frame - 1; f >= 0; f--) { fr = timeline.getFrame(f); ds = fr == null ? null : fr.layers.get(depth); - boolean empty = ds == null || ds.getCharacter() == null; + boolean empty = emptyFrames.contains(f) || ds == null || ds.getCharacter() == null; if (!empty) { somethingBefore = true; break; @@ -630,7 +648,7 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe for (int f = frame + 1; f < timeline.getFrameCount(); f++) { fr = timeline.getFrame(f); ds = fr == null ? null : fr.layers.get(depth); - boolean empty = ds == null || ds.getCharacter() == null; + boolean empty = emptyFrames.contains(f) || ds == null || ds.getCharacter() == null; if (!empty) { somethingAfter = true; break; @@ -700,103 +718,108 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe int nf = p.x; int nd = p.y; - int f = timelined.getFrameCount(); - List lastFrameDepthTags = new ArrayList<>(); - DepthState ds = timeline.getFrame(nf).layers.get(nd); - if (ds != null && ds.key) { - PlaceObjectTypeTag po = ds.placeObjectTag; - ShowFrameTag sf = timeline.getFrame(nf).showFrameTag; - int pos = sf == null ? tags.size() : timelined.indexOfTag(sf); - for (int i = pos + 1; i < tags.size(); i++) { + if (timelined instanceof ButtonTag) { + ButtonTag button = (ButtonTag) timelined; + BUTTONRECORD rec = button.getButtonRecordAt(nf, nd, false); + if (rec != null) { + rec.setFrame(nf, false); + } + for(int i = nf + 1; i < button.getFrameCount(); i++) { + rec = button.getButtonRecordAt(i, nd, false); + if (rec != null) { + rec.setFrame(i - 1, true); + rec.setFrame(i, false); + } + } + button.packRecords(); + } else { + + int f = timelined.getFrameCount(); + List lastFrameDepthTags = new ArrayList<>(); + DepthState ds = timeline.getFrame(nf).layers.get(nd); + if (ds != null && ds.key) { + PlaceObjectTypeTag po = ds.placeObjectTag; + ShowFrameTag sf = timeline.getFrame(nf).showFrameTag; + int pos = sf == null ? tags.size() : timelined.indexOfTag(sf); + for (int i = pos + 1; i < tags.size(); i++) { + Tag t = tags.get(i); + if (t instanceof RemoveObject2Tag) { + RemoveObject2Tag rt = (RemoveObject2Tag) t; + if (rt.depth == nd) { + timelined.removeTag(po); + timelined.removeTag(rt); + i--; + i--; + } + } + if (t instanceof ShowFrameTag) { + break; + } + } + } + + boolean endsWithRemove = false; + for (int i = tags.size() - 1; i >= 0; i--) { Tag t = tags.get(i); - if (t instanceof RemoveObject2Tag) { - RemoveObject2Tag rt = (RemoveObject2Tag) t; - if (rt.depth == nd) { - timelined.removeTag(po); - timelined.removeTag(rt); - i--; - i--; + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag pt = (PlaceObjectTypeTag) t; + if (pt.getDepth() == nd) { + break; + } + } + if (t instanceof RemoveTag) { + RemoveTag rt = (RemoveTag) t; + if (rt.getDepth() == nd) { + endsWithRemove = true; + break; + } + } + } + + for (int i = tags.size() - 1; i >= 0; i--) { + Tag t = tags.get(i); + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag pt = (PlaceObjectTypeTag) t; + if (pt.getDepth() == nd) { + lastFrameDepthTags.add(pt); + timelined.removeTag(i); + } + } + if (t instanceof RemoveTag) { + RemoveTag rt = (RemoveTag) t; + if (rt.getDepth() == nd) { + lastFrameDepthTags.add(rt); + timelined.removeTag(i); } } if (t instanceof ShowFrameTag) { - break; + for (Tag lt : lastFrameDepthTags) { + timelined.addTag(i, lt); + } + lastFrameDepthTags.clear(); + f--; + if (f == nf) { + break; + } } } - } - boolean endsWithRemove = false; - for (int i = tags.size() - 1; i >= 0; i--) { - Tag t = tags.get(i); - if (t instanceof PlaceObjectTypeTag) { - PlaceObjectTypeTag pt = (PlaceObjectTypeTag) t; - if (pt.getDepth() == nd) { - break; + if (!endsWithRemove) { + RemoveTag rt = new RemoveObject2Tag(timelined.getSwf()); + rt.setTimelined(timelined); + rt.setDepth(nd); + Tag lt = tags.get(tags.size() - 1); + if (lt instanceof ShowFrameTag) { + timelined.addTag(tags.size() - 1, rt); + } else { + timelined.addTag(lt); } } - if (t instanceof RemoveTag) { - RemoveTag rt = (RemoveTag) t; - if (rt.getDepth() == nd) { - endsWithRemove = true; - break; - } - } - } - - for (int i = tags.size() - 1; i >= 0; i--) { - Tag t = tags.get(i); - if (t instanceof PlaceObjectTypeTag) { - PlaceObjectTypeTag pt = (PlaceObjectTypeTag) t; - if (pt.getDepth() == nd) { - lastFrameDepthTags.add(pt); - timelined.removeTag(i); - } - } - if (t instanceof RemoveTag) { - RemoveTag rt = (RemoveTag) t; - if (rt.getDepth() == nd) { - lastFrameDepthTags.add(rt); - timelined.removeTag(i); - } - } - if (t instanceof ShowFrameTag) { - for (Tag lt : lastFrameDepthTags) { - timelined.addTag(i, lt); - } - lastFrameDepthTags.clear(); - f--; - if (f == nf) { - break; - } - } - } - - if (!endsWithRemove) { - RemoveTag rt = new RemoveObject2Tag(timelined.getSwf()); - rt.setTimelined(timelined); - rt.setDepth(nd); - Tag lt = tags.get(tags.size() - 1); - if (lt instanceof ShowFrameTag) { - timelined.addTag(tags.size() - 1, rt); - } else { - timelined.addTag(lt); - } } } - + timelined.resetTimeline(); - /*System.err.println("=====AFTER======="); - f = 0; - int i = 0; - for (Tag t : timelined.getTags()) { - if (t instanceof ShowFrameTag) { - System.err.println("" + i + ": frame " + f); - f++; - } else { - System.err.println("" + i + ": " + t); - } - i++; - }*/ Point firstCursor = orderedCursor.iterator().next(); frameSelect(firstCursor.x, firstCursor.y); timeline = timelined.getTimeline(); @@ -851,9 +874,22 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe continue; } ds.key = true; - /*RemoveTag rm = new RemoveObject2Tag(timelined.getSwf()); - rm.setDepth(depth); - rm.setTimelined(timelined);*/ + + if (timelined instanceof ButtonTag) { + ButtonTag button = (ButtonTag) timelined; + BUTTONRECORD rec = button.getButtonRecordAt(nf, nd, false); + if (rec != null) { + BUTTONRECORD rec2 = new BUTTONRECORD(rec.getSwf(), rec.getTag()); + rec2.fromPlaceObject(rec.toPlaceObject()); + + for(int i = nf; i < button.getFrameCount(); i++) { + rec2.setFrame(i, rec.hasFrame(i)); + rec.setFrame(i, false); + } + button.getRecords().add(rec2); + } + continue; + } PlaceObjectTypeTag place; try { place = (PlaceObjectTypeTag) ds.placeObjectTag.cloneTag(); @@ -870,8 +906,6 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe } else { pos = timelined.getTags().size(); } - //timelined.addTag(pos++, rm); - timelined.addTag(pos++, place); } @@ -910,7 +944,10 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe DepthState ds = null; - if (fframe >= timelined.getFrameCount()) { + if (!(timelined instanceof ButtonTag) && fframe >= timelined.getFrameCount()) { + if (timelined instanceof ButtonTag) { + + } int lastFrame = timelined.getFrameCount() - 1; for (int d = 1; d <= timeline.maxDepth; d++) { ds = timeline.getDepthState(lastFrame, d); @@ -931,30 +968,41 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe timelined.resetTimeline(); } + int nonEmptyFrame = -1; for (int f = fframe - 1; f >= 0; f--) { ds = timeline.getDepthState(f, fdepth); if (ds != null && ds.getCharacter() != null) { + nonEmptyFrame = f; break; } } - - PlaceObjectTypeTag place; - try { - place = (PlaceObjectTypeTag) ds.placeObjectTag.cloneTag(); - } catch (InterruptedException | IOException ex) { - //should not happen - return; + if (timelined instanceof ButtonTag) { + ButtonTag button = (ButtonTag) timelined; + BUTTONRECORD rec = button.getButtonRecordAt(nonEmptyFrame, fdepth, false); + BUTTONRECORD rec2 = new BUTTONRECORD(rec.getSwf(), rec.getTag()); + rec2.fromPlaceObject(rec.toPlaceObject()); + rec2.setFrame(fframe, true); + button.getRecords().add(rec2); + + } else { + PlaceObjectTypeTag place; + try { + place = (PlaceObjectTypeTag) ds.placeObjectTag.cloneTag(); + } catch (InterruptedException | IOException ex) { + //should not happen + return; + } + place.setTimelined(timelined); + place.setPlaceFlagMove(false); + ShowFrameTag sf = timeline.getFrame(fframe).showFrameTag; + int pos; + if (sf != null) { + pos = timelined.indexOfTag(sf); + } else { + pos = timelined.getTags().size(); + } + timelined.addTag(pos++, place); } - place.setTimelined(timelined); - place.setPlaceFlagMove(false); - ShowFrameTag sf = timeline.getFrame(fframe).showFrameTag; - int pos; - if (sf != null) { - pos = timelined.indexOfTag(sf); - } else { - pos = timelined.getTags().size(); - } - timelined.addTag(pos++, place); timelined.resetTimeline(); timeline = timelined.getTimeline(); @@ -990,14 +1038,17 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe DepthState ds; Timelined timelined = timeline.timelined; - //for (int nf = 0; nf < fendFrame - fframe + 1; nf++) { - // for (int nd = fdepth; nd <= fendDepth; nd++) for (Point p : orderedCursor) { int nf = p.x; int nd = p.y; + if (nf >= timelined.getFrameCount()) { + if (timelined instanceof ButtonTag) { + continue; + } + ReadOnlyTagList tags = timelined.getTags(); for (int i = tags.size() - 1; i >= 0; i--) { Tag t = tags.get(i); @@ -1042,9 +1093,13 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe } boolean somethingAfter = false; + Set emptyFrames = new LinkedHashSet<>(); + if (timelined instanceof ButtonTag) { + emptyFrames = ((ButtonTag) timelined).getEmptyFrames(); + } for (int f = nf + 1; f < timeline.getFrameCount(); f++) { ds = timeline.getDepthState(f, nd); - boolean empty = ds == null || ds.getCharacter() == null; + boolean empty = emptyFrames.contains(f) || ds == null || ds.getCharacter() == null; if (!empty) { somethingAfter = true; break; @@ -1053,10 +1108,29 @@ public class TimelineBodyPanel extends JPanel implements MouseListener, KeyListe for (int f = nf; f >= 0; f--) { ds = timeline.getDepthState(f, nd); - boolean empty = ds == null || ds.getCharacter() == null; + boolean empty = emptyFrames.contains(f) || ds == null || ds.getCharacter() == null; if (!empty || somethingAfter) { int moveFrameCount = somethingAfter ? 1 : nf - f; for (int mf = 0; mf < moveFrameCount; mf++) { + + if (timelined instanceof ButtonTag) { + ButtonTag button = (ButtonTag) timelined; + if (!somethingAfter) { + BUTTONRECORD rec = button.getButtonRecordAt(f, nd, true); + rec.setFrame(nf - mf, true); + } else { + for (int fx = button.getFrameCount() - 1; fx >= nf; fx--) { + BUTTONRECORD rec = button.getButtonRecordAt(fx, nd, false); + if (rec != null) { + rec.setFrame(fx, false); + rec.setFrame(fx + 1, true); + } + } + } + button.packRecords(); + continue; + } + int pos = timelined.indexOfTag(timeline.getFrame(f).showFrameTag); ReadOnlyTagList tags = timelined.getTags(); List lastFrameDepthTags = new ArrayList<>(); diff --git a/src/com/jpexs/decompiler/flash/easygui/TimelinedTagListDoableOperation.java b/src/com/jpexs/decompiler/flash/easygui/TimelinedTagListDoableOperation.java index 531b55ccb..b2234b846 100644 --- a/src/com/jpexs/decompiler/flash/easygui/TimelinedTagListDoableOperation.java +++ b/src/com/jpexs/decompiler/flash/easygui/TimelinedTagListDoableOperation.java @@ -18,7 +18,10 @@ package com.jpexs.decompiler.flash.easygui; import com.jpexs.decompiler.flash.ReadOnlyTagList; import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.timeline.Timelined; +import com.jpexs.decompiler.flash.types.BUTTONRECORD; +import java.util.ArrayList; import java.util.List; /** @@ -31,6 +34,7 @@ public abstract class TimelinedTagListDoableOperation implements DoableOperation private final Timelined timelined; protected List tags; + protected List buttonRecords; protected boolean wasModified = false; protected int fframe; protected List fdepths; @@ -53,10 +57,26 @@ public abstract class TimelinedTagListDoableOperation implements DoableOperation } protected void saveTagList() { - tags = timelined.getTags().toArrayList(); + if (timelined instanceof ButtonTag) { + List recordsCopy = new ArrayList<>(); + for (BUTTONRECORD rec :((ButtonTag) timelined).getRecords()) { + recordsCopy.add(new BUTTONRECORD(rec)); + } + buttonRecords = recordsCopy; + } else { + tags = timelined.getTags().toArrayList(); + } } - protected void restoreTagList() { + protected void restoreTagList() { + if (buttonRecords != null) { + if (timelined instanceof ButtonTag) { + ButtonTag button = (ButtonTag) timelined; + button.getRecords().clear(); + button.getRecords().addAll(buttonRecords); + } + timelined.resetTimeline(); + } if (tags != null) { ReadOnlyTagList newTags = timelined.getTags(); int size = newTags.size(); diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index e37157b7f..bd2024099 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -3454,6 +3454,9 @@ public final class ImagePanel extends JPanel implements MediaDisplay { BoundedTag bounded = (BoundedTag) timelined; RECT rect = bounded.getRect(); + if (rect == null) { + return; + } int width = rect.getWidth(); double scale = 1.0; /*if (width > swf.displayRect.getWidth()) { @@ -4140,6 +4143,10 @@ public final class ImagePanel extends JPanel implements MediaDisplay { return; } RECT timRect = timelined.getRect(); + + if (timRect == null) { + return; + } /* int h_value = horizontalScrollBar.getValue(); @@ -4813,6 +4820,9 @@ public final class ImagePanel extends JPanel implements MediaDisplay { Matrix parentMatrix = new Matrix(); for (int i = 0; i < parentTimelineds.size(); i++) { DepthState parentDepthState = parentTimelineds.get(i).getTimeline().getDepthState(parentFrames.get(i), parentDepths.get(i)); + if (parentDepthState == null) { + continue; + } parentMatrix = parentMatrix.concatenate(new Matrix(parentDepthState.matrix)); } @@ -4851,7 +4861,11 @@ public final class ImagePanel extends JPanel implements MediaDisplay { for (int i = 0; i < selectedDepths.size(); i++) { if (newMatrix != null) { - DepthState ds = timeline.getFrame(frame).layers.get(selectedDepths.get(i)); + Frame fr = timeline.getFrame(frame); + if (fr == null) { + continue; + } + DepthState ds = fr.layers.get(selectedDepths.get(i)); if (ds != null) { ds.temporaryMatrix = newMatrix.concatenate(new Matrix(ds.matrix)).toMATRIX(); }