/*
* Copyright (C) 2010-2016 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 <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.gui;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.action.parser.ActionParseException;
import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
import com.jpexs.decompiler.flash.gui.controls.JPersistentSplitPane;
import com.jpexs.decompiler.flash.gui.debugger.DebuggerTools;
import com.jpexs.decompiler.flash.gui.editor.LineMarkedEditorPane;
import com.jpexs.decompiler.flash.gui.player.FlashPlayerPanel;
import com.jpexs.decompiler.flash.gui.player.MediaDisplay;
import com.jpexs.decompiler.flash.gui.player.PlayerControls;
import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag;
import com.jpexs.decompiler.flash.tags.DefineBitsTag;
import com.jpexs.decompiler.flash.tags.DefineMorphShape2Tag;
import com.jpexs.decompiler.flash.tags.DefineMorphShapeTag;
import com.jpexs.decompiler.flash.tags.DefineSoundTag;
import com.jpexs.decompiler.flash.tags.DefineSpriteTag;
import com.jpexs.decompiler.flash.tags.DefineTextTag;
import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag;
import com.jpexs.decompiler.flash.tags.DoActionTag;
import com.jpexs.decompiler.flash.tags.DoInitActionTag;
import com.jpexs.decompiler.flash.tags.EndTag;
import com.jpexs.decompiler.flash.tags.ExportAssetsTag;
import com.jpexs.decompiler.flash.tags.FileAttributesTag;
import com.jpexs.decompiler.flash.tags.JPEGTablesTag;
import com.jpexs.decompiler.flash.tags.MetadataTag;
import com.jpexs.decompiler.flash.tags.PlaceObject2Tag;
import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag;
import com.jpexs.decompiler.flash.tags.ShowFrameTag;
import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.VideoFrameTag;
import com.jpexs.decompiler.flash.tags.base.AloneTag;
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
import com.jpexs.decompiler.flash.tags.base.CharacterIdTag;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.FontTag;
import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag;
import com.jpexs.decompiler.flash.tags.base.RemoveTag;
import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag;
import com.jpexs.decompiler.flash.tags.base.TextTag;
import com.jpexs.decompiler.flash.tags.gfx.DefineCompactedFont;
import com.jpexs.decompiler.flash.timeline.DepthState;
import com.jpexs.decompiler.flash.timeline.Frame;
import com.jpexs.decompiler.flash.timeline.TagScript;
import com.jpexs.decompiler.flash.timeline.Timelined;
import com.jpexs.decompiler.flash.treeitems.TreeItem;
import com.jpexs.decompiler.flash.types.GLYPHENTRY;
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.SHAPE;
import com.jpexs.decompiler.flash.types.TEXTRECORD;
import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
import com.jpexs.helpers.SerializableImage;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
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.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.SwingConstants;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
/**
*
* @author JPEXS
*/
public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel {
private static final String FLASH_VIEWER_CARD = "FLASHVIEWER";
private static final String DRAW_PREVIEW_CARD = "DRAWPREVIEW";
private static final String GENERIC_TAG_CARD = "GENERICTAG";
private static final String BINARY_TAG_CARD = "BINARYTAG";
private static final String METADATA_TAG_CARD = "METADATATAG";
private static final String EMPTY_CARD = "EMPTY";
private static final String CARDTEXTPANEL = "Text card";
private static final String CARDFONTPANEL = "Font card";
private final MainPanel mainPanel;
private final JPanel viewerCards;
private final FlashPlayerPanel flashPanel;
private File tempFile;
private ImagePanel imagePanel;
private PlayerControls imagePlayControls;
private MediaDisplay media;
private BinaryPanel binaryPanel;
private LineMarkedEditorPane metadataEditor;
private GenericTagPanel genericTagPanel;
private JPanel displayWithPreview;
// Image tag buttons
private JButton replaceImageButton;
private JButton replaceImageAlphaButton;
private JButton prevFontsButton;
private JButton nextFontsButton;
// Binary tag buttons
private JButton replaceBinaryButton;
// Metadata editor buttons
private JButton metadataEditButton;
private JButton metadataSaveButton;
private JButton metadataCancelButton;
// Generic tag buttons
private JButton genericEditButton;
private JButton genericSaveButton;
private JButton genericCancelButton;
private JPanel parametersPanel;
private FontPanel fontPanel;
private int fontPageNum;
private TextPanel textPanel;
private MetadataTag metadataTag;
private boolean readOnly = false;
private final int dividerSize;
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
setDividerSize(this.readOnly ? 0 : dividerSize);
if (readOnly) {
parametersPanel.setVisible(false);
}
}
public PreviewPanel(MainPanel mainPanel, FlashPlayerPanel flashPanel) {
super(JSplitPane.HORIZONTAL_SPLIT, Configuration.guiPreviewSplitPaneDividerLocationPercent);
this.mainPanel = mainPanel;
this.flashPanel = flashPanel;
viewerCards = new JPanel();
viewerCards.setLayout(new CardLayout());
viewerCards.add(createFlashPlayerPanel(flashPanel), FLASH_VIEWER_CARD);
viewerCards.add(createImagesCard(), DRAW_PREVIEW_CARD);
viewerCards.add(createBinaryCard(), BINARY_TAG_CARD);
viewerCards.add(createMetadataCard(), METADATA_TAG_CARD);
viewerCards.add(createGenericTagCard(), GENERIC_TAG_CARD);
viewerCards.add(createEmptyCard(), EMPTY_CARD);
setLeftComponent(viewerCards);
createParametersPanel();
showCardLeft(FLASH_VIEWER_CARD);
dividerSize = getDividerSize();
}
private JPanel createEmptyCard() {
JPanel ret = new JPanel();
ret.add(new JLabel("-"));
return ret;
}
private void createParametersPanel() {
displayWithPreview = new JPanel(new CardLayout());
textPanel = new TextPanel(mainPanel);
displayWithPreview.add(textPanel, CARDTEXTPANEL);
fontPanel = new FontPanel(mainPanel);
displayWithPreview.add(fontPanel, CARDFONTPANEL);
JLabel paramsLabel = new HeaderLabel(mainPanel.translate("parameters"));
paramsLabel.setHorizontalAlignment(SwingConstants.CENTER);
//paramsLabel.setBorder(new BevelBorder(BevelBorder.RAISED));
parametersPanel = new JPanel(new BorderLayout());
parametersPanel.add(paramsLabel, BorderLayout.NORTH);
parametersPanel.add(displayWithPreview, BorderLayout.CENTER);
setRightComponent(parametersPanel);
}
private JPanel createImageButtonsPanel() {
replaceImageButton = new JButton(mainPanel.translate("button.replace"), View.getIcon("replaceimage16"));
replaceImageButton.setMargin(new Insets(3, 3, 3, 10));
replaceImageButton.addActionListener(mainPanel::replaceButtonActionPerformed);
replaceImageButton.setVisible(false);
replaceImageAlphaButton = new JButton(mainPanel.translate("button.replaceAlphaChannel"), View.getIcon("replacealpha16"));
replaceImageAlphaButton.setMargin(new Insets(3, 3, 3, 10));
replaceImageAlphaButton.addActionListener(mainPanel::replaceAlphaButtonActionPerformed);
replaceImageAlphaButton.setVisible(false);
prevFontsButton = new JButton(mainPanel.translate("button.prev"), View.getIcon("prev16"));
prevFontsButton.setMargin(new Insets(3, 3, 3, 10));
prevFontsButton.addActionListener(this::prevFontsButtonActionPerformed);
prevFontsButton.setVisible(false);
nextFontsButton = new JButton(mainPanel.translate("button.next"), View.getIcon("next16"));
nextFontsButton.setMargin(new Insets(3, 3, 3, 10));
nextFontsButton.addActionListener(this::nextFontsButtonActionPerformed);
nextFontsButton.setVisible(false);
ButtonsPanel imageButtonsPanel = new ButtonsPanel();
imageButtonsPanel.add(replaceImageButton);
imageButtonsPanel.add(replaceImageAlphaButton);
imageButtonsPanel.add(prevFontsButton);
imageButtonsPanel.add(nextFontsButton);
return imageButtonsPanel;
}
private JPanel createBinaryButtonsPanel() {
replaceBinaryButton = new JButton(mainPanel.translate("button.replace"), View.getIcon("edit16"));
replaceBinaryButton.setMargin(new Insets(3, 3, 3, 10));
replaceBinaryButton.addActionListener(mainPanel::replaceButtonActionPerformed);
ButtonsPanel binaryButtonsPanel = new ButtonsPanel();
binaryButtonsPanel.add(replaceBinaryButton);
return binaryButtonsPanel;
}
private JPanel createGenericTagButtonsPanel() {
genericEditButton = new JButton(mainPanel.translate("button.edit"), View.getIcon("edit16"));
genericEditButton.setMargin(new Insets(3, 3, 3, 10));
genericEditButton.addActionListener(this::editGenericTagButtonActionPerformed);
genericSaveButton = new JButton(mainPanel.translate("button.save"), View.getIcon("save16"));
genericSaveButton.setMargin(new Insets(3, 3, 3, 10));
genericSaveButton.addActionListener(this::saveGenericTagButtonActionPerformed);
genericSaveButton.setVisible(false);
genericCancelButton = new JButton(mainPanel.translate("button.cancel"), View.getIcon("cancel16"));
genericCancelButton.setMargin(new Insets(3, 3, 3, 10));
genericCancelButton.addActionListener(this::cancelGenericTagButtonActionPerformed);
genericCancelButton.setVisible(false);
ButtonsPanel genericTagButtonsPanel = new ButtonsPanel();
genericTagButtonsPanel.add(genericEditButton);
genericTagButtonsPanel.add(genericSaveButton);
genericTagButtonsPanel.add(genericCancelButton);
return genericTagButtonsPanel;
}
private JPanel createMetadataButtonsPanel() {
metadataEditButton = new JButton(mainPanel.translate("button.edit"), View.getIcon("edit16"));
metadataEditButton.setMargin(new Insets(3, 3, 3, 10));
metadataEditButton.addActionListener(this::editMetadataButtonActionPerformed);
metadataSaveButton = new JButton(mainPanel.translate("button.save"), View.getIcon("save16"));
metadataSaveButton.setMargin(new Insets(3, 3, 3, 10));
metadataSaveButton.addActionListener(this::saveMetadataButtonActionPerformed);
metadataSaveButton.setVisible(false);
metadataCancelButton = new JButton(mainPanel.translate("button.cancel"), View.getIcon("cancel16"));
metadataCancelButton.setMargin(new Insets(3, 3, 3, 10));
metadataCancelButton.addActionListener(this::cancelMetadataButtonActionPerformed);
metadataCancelButton.setVisible(false);
ButtonsPanel metadataTagButtonsPanel = new ButtonsPanel();
metadataTagButtonsPanel.add(metadataEditButton);
metadataTagButtonsPanel.add(metadataSaveButton);
metadataTagButtonsPanel.add(metadataCancelButton);
return metadataTagButtonsPanel;
}
private JPanel createFlashPlayerPanel(FlashPlayerPanel flashPanel) {
JPanel pan = new JPanel(new BorderLayout());
JLabel prevLabel = new HeaderLabel(mainPanel.translate("swfpreview"));
prevLabel.setHorizontalAlignment(SwingConstants.CENTER);
//prevLabel.setBorder(new BevelBorder(BevelBorder.RAISED));
pan.add(prevLabel, BorderLayout.NORTH);
Component leftComponent;
if (flashPanel != null) {
JPanel flashPlayPanel = new JPanel(new BorderLayout());
flashPlayPanel.add(flashPanel, BorderLayout.CENTER);
/*JPanel bottomPanel = new JPanel(new BorderLayout());
JPanel buttonsPanel = new JPanel(new FlowLayout());
JButton selectColorButton = new JButton(View.getIcon("color16"));
selectColorButton.addActionListener(mainPanel::selectBkColor);
selectColorButton.setToolTipText(AppStrings.translate("button.selectbkcolor.hint"));
buttonsPanel.add(selectColorButton);
bottomPanel.add(buttonsPanel, BorderLayout.EAST);
flashPlayPanel.add(bottomPanel, BorderLayout.SOUTH);*/
JPanel flashPlayPanel2 = new JPanel(new BorderLayout());
flashPlayPanel2.add(flashPlayPanel, BorderLayout.CENTER);
flashPlayPanel2.add(new PlayerControls(mainPanel, flashPanel), BorderLayout.SOUTH);
leftComponent = flashPlayPanel2;
} else {
JPanel swtPanel = new JPanel(new BorderLayout());
swtPanel.add(new JLabel("<html><center>" + mainPanel.translate("notavailonthisplatform") + "</center></html>", JLabel.CENTER), BorderLayout.CENTER);
swtPanel.setBackground(View.getDefaultBackgroundColor());
leftComponent = swtPanel;
}
pan.add(leftComponent, BorderLayout.CENTER);
return pan;
}
private JPanel createImagesCard() {
JPanel shapesCard = new JPanel(new BorderLayout());
JPanel previewPanel = new JPanel(new BorderLayout());
JPanel previewCnt = new JPanel(new BorderLayout());
imagePanel = new ImagePanel();
imagePanel.setLoop(Configuration.loopMedia.get());
previewCnt.add(imagePanel, BorderLayout.CENTER);
previewCnt.add(imagePlayControls = new PlayerControls(mainPanel, imagePanel), BorderLayout.SOUTH);
imagePlayControls.setMedia(imagePanel);
previewPanel.add(previewCnt, BorderLayout.CENTER);
JLabel prevIntLabel = new HeaderLabel(mainPanel.translate("swfpreview.internal"));
prevIntLabel.setHorizontalAlignment(SwingConstants.CENTER);
//prevIntLabel.setBorder(new BevelBorder(BevelBorder.RAISED));
previewPanel.add(prevIntLabel, BorderLayout.NORTH);
shapesCard.add(previewPanel, BorderLayout.CENTER);
shapesCard.add(createImageButtonsPanel(), BorderLayout.SOUTH);
return shapesCard;
}
private JPanel createMetadataCard() {
JPanel metadataCard = new JPanel(new BorderLayout());
metadataEditor = new LineMarkedEditorPane();
metadataCard.add(new JScrollPane(metadataEditor), BorderLayout.CENTER);
//metadataEditor.setContentType("text/xml");
metadataEditor.setEditable(false);
metadataEditor.setFont(Configuration.getSourceFont());
metadataEditor.changeContentType("text/xml");
metadataEditor.addTextChangedListener(this::metadataTextChanged);
metadataCard.add(createMetadataButtonsPanel(), BorderLayout.SOUTH);
return metadataCard;
}
private boolean isMetadataModified() {
return metadataSaveButton.isVisible() && metadataSaveButton.isEnabled();
}
private void setMetadataModified(boolean value) {
metadataSaveButton.setEnabled(value);
metadataCancelButton.setEnabled(value);
}
private void metadataTextChanged() {
setMetadataModified(true);
}
private void updateMetadataButtonsVisibility() {
boolean edit = metadataEditor.isEditable();
boolean editorMode = Configuration.editorMode.get();
metadataEditButton.setVisible(!readOnly && !edit);
metadataSaveButton.setVisible(!readOnly && edit);
boolean metadataModified = isMetadataModified();
metadataCancelButton.setVisible(!readOnly && edit);
metadataCancelButton.setEnabled(metadataModified || !editorMode);
}
private JPanel createBinaryCard() {
JPanel binaryCard = new JPanel(new BorderLayout());
binaryPanel = new BinaryPanel(mainPanel);
binaryCard.add(binaryPanel, BorderLayout.CENTER);
binaryCard.add(createBinaryButtonsPanel(), BorderLayout.SOUTH);
return binaryCard;
}
private JPanel createGenericTagCard() {
JPanel genericTagCard = new JPanel(new BorderLayout());
genericTagPanel = new GenericTagTreePanel(mainPanel);
genericTagCard.add(genericTagPanel, BorderLayout.CENTER);
genericTagCard.add(createGenericTagButtonsPanel(), BorderLayout.SOUTH);
return genericTagCard;
}
private void showCardLeft(String card) {
CardLayout cl = (CardLayout) (viewerCards.getLayout());
cl.show(viewerCards, card);
}
private void showCardRight(String card) {
CardLayout cl = (CardLayout) (displayWithPreview.getLayout());
cl.show(displayWithPreview, card);
}
public TextPanel getTextPanel() {
return textPanel;
}
public void setParametersPanelVisible(boolean show) {
parametersPanel.setVisible(show);
}
public void showFlashViewerPanel() {
parametersPanel.setVisible(false);
showCardLeft(FLASH_VIEWER_CARD);
}
public void showImagePanel(Timelined timelined, SWF swf, int frame) {
showCardLeft(DRAW_PREVIEW_CARD);
parametersPanel.setVisible(false);
imagePlayControls.setMedia(imagePanel);
imagePanel.setTimelined(timelined, swf, frame);
}
public void showImagePanel(SerializableImage image) {
showCardLeft(DRAW_PREVIEW_CARD);
parametersPanel.setVisible(false);
imagePlayControls.setMedia(imagePanel);
imagePanel.setImage(image);
}
public void showTextComparePanel(TextTag textTag, TextTag newTextTag) {
imagePanel.setText(textTag, newTextTag);
}
public void setMedia(MediaDisplay media) {
this.media = media;
imagePlayControls.setMedia(media);
}
public void showFontPanel(FontTag fontTag) {
fontPageNum = 0;
showFontPage(fontTag);
showCardRight(CARDFONTPANEL);
if (!readOnly) {
parametersPanel.setVisible(true);
}
fontPanel.showFontTag(fontTag);
int pageCount = getFontPageCount(fontTag);
if (pageCount > 1) {
prevFontsButton.setVisible(true);
nextFontsButton.setVisible(true);
}
}
private void showFontPage(FontTag fontTag) {
if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) {
showImagePanel(MainPanel.makeTimelined(fontTag), fontTag.getSwf(), fontPageNum);
}
}
public static int getFontPageCount(FontTag fontTag) {
int pageCount = (fontTag.getGlyphShapeTable().size() - 1) / SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW + 1;
if (pageCount < 1) {
pageCount = 1;
}
return pageCount;
}
public void showEmpty() {
showCardLeft(EMPTY_CARD);
}
public void showTextPanel(TextTag textTag) {
if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) {
showImagePanel(MainPanel.makeTimelined(textTag), textTag.getSwf(), 0);
}
showCardRight(CARDTEXTPANEL);
if (!readOnly) {
parametersPanel.setVisible(true);
}
textPanel.setText(textTag);
}
public void focusTextPanel() {
textPanel.focusTextValue();
}
public void clear() {
imagePanel.clearAll();
if (media != null) {
try {
media.close();
} catch (IOException ex) {
// ignore
}
}
binaryPanel.setBinaryData(null);
genericTagPanel.clear();
fontPanel.clear();
}
public void closeTag() {
textPanel.closeTag();
}
public static String formatMetadata(String input, int indent) {
input = input.replace("> <", "><");
try {
Source xmlInput = new StreamSource(new StringReader(input));
StringWriter stringWriter = new StringWriter();
StreamResult xmlOutput = new StreamResult(stringWriter);
StringWriter sw = new StringWriter();
xmlOutput.setWriter(sw);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setAttribute("indent-number", indent);
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "" + indent);
transformer.transform(xmlInput, xmlOutput);
return xmlOutput.getWriter().toString();
} catch (IllegalArgumentException | TransformerException e) {
return input;
}
}
public void showMetaDataPanel(MetadataTag metadataTag) {
showCardLeft(METADATA_TAG_CARD);
this.metadataTag = metadataTag;
metadataEditor.setEditable(!readOnly && !metadataTag.isReadOnly() && Configuration.editorMode.get());
metadataEditor.setText(formatMetadata(metadataTag.xmlMetadata, 4));
setMetadataModified(false);
updateMetadataButtonsVisibility();
parametersPanel.setVisible(false);
}
public void showBinaryPanel(DefineBinaryDataTag binaryDataTag) {
showCardLeft(BINARY_TAG_CARD);
binaryPanel.setBinaryData(binaryDataTag);
parametersPanel.setVisible(false);
}
public void showGenericTagPanel(Tag tag) {
showCardLeft(GENERIC_TAG_CARD);
genericEditButton.setVisible(!tag.isReadOnly());
genericSaveButton.setVisible(false);
genericCancelButton.setVisible(false);
genericTagPanel.setEditMode(false, tag);
parametersPanel.setVisible(false);
}
public void setImageReplaceButtonVisible(boolean show, boolean showAlpha) {
if (readOnly) {
show = false;
showAlpha = false;
}
replaceImageButton.setVisible(show);
replaceImageAlphaButton.setVisible(showAlpha);
prevFontsButton.setVisible(false);
nextFontsButton.setVisible(false);
}
private static Tag classicTag(Tag t) {
if (t instanceof DefineCompactedFont) {
return ((DefineCompactedFont) t).toClassicFont();
}
return t;
}
private static void writeTag(Tag t, SWFOutputStream sos) throws IOException {
t = classicTag(t);
t.writeTag(sos);
if (t instanceof CharacterIdTag) {
List<CharacterIdTag> chIdTags = t.getSwf().getCharacterIdTags(((CharacterIdTag) t).getCharacterId());
if (chIdTags != null) {
for (CharacterIdTag chIdTag : chIdTags) {
if (!(chIdTag instanceof PlaceObjectTypeTag || chIdTag instanceof RemoveTag)) {
((Tag) chIdTag).writeTag(sos);
}
}
}
}
}
public void createAndShowTempSwf(TreeItem treeItem) {
SWF swf = null;
try {
if (tempFile != null) {
tempFile.delete();
}
tempFile = File.createTempFile("ffdec_view_", ".swf");
tempFile.deleteOnExit();
Color backgroundColor = View.getSwfBackgroundColor();
if (treeItem instanceof Tag) {
Tag tag = (Tag) treeItem;
swf = tag.getSwf();
if (tag instanceof FontTag) { //Fonts are always black on white
backgroundColor = View.getDefaultBackgroundColor();
}
} else if (treeItem instanceof Frame) {
Frame fn = (Frame) treeItem;
swf = fn.getSwf();
if (fn.timeline.timelined == swf) {
SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor();
if (setBgColorTag != null) {
backgroundColor = setBgColorTag.backgroundColor.toColor();
}
}
}
int frameCount = 1;
float frameRate = swf.frameRate;
HashMap<Integer, VideoFrameTag> videoFrames = new HashMap<>();
if (treeItem instanceof DefineVideoStreamTag) {
DefineVideoStreamTag vs = (DefineVideoStreamTag) treeItem;
SWF.populateVideoFrames(vs.getCharacterId(), swf.getTags(), videoFrames);
frameCount = videoFrames.size();
}
List<SoundStreamBlockTag> soundFrames = new ArrayList<>();
if (treeItem instanceof SoundStreamHeadTypeTag) {
soundFrames = ((SoundStreamHeadTypeTag) treeItem).getBlocks();
frameCount = soundFrames.size();
}
if ((treeItem instanceof DefineMorphShapeTag) || (treeItem instanceof DefineMorphShape2Tag)) {
frameRate = MainPanel.MORPH_SHAPE_ANIMATION_FRAME_RATE;
frameCount = (int) (MainPanel.MORPH_SHAPE_ANIMATION_LENGTH * frameRate);
}
if (treeItem instanceof DefineSoundTag) {
frameCount = 1;
}
if (treeItem instanceof DefineSpriteTag) {
frameCount = ((DefineSpriteTag) treeItem).frameCount;
}
byte[] data;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
SWFOutputStream sos2 = new SWFOutputStream(baos, SWF.DEFAULT_VERSION);
RECT outrect = new RECT(swf.displayRect);
RECT treeItemBounds = null;
if (treeItem instanceof FontTag) {
outrect.Xmin = 0;
outrect.Ymin = 0;
outrect.Xmax = FontTag.PREVIEWSIZE * 20;
outrect.Ymax = FontTag.PREVIEWSIZE * 20;
} else if (treeItem instanceof BoundedTag) {
treeItemBounds = ((BoundedTag) treeItem).getRect();
} else if (treeItem instanceof Frame) {
treeItemBounds = ((Frame) treeItem).timeline.timelined.getRect();
}
if (treeItemBounds != null) {
if (outrect.getWidth() < treeItemBounds.getWidth()) {
outrect.Xmax += treeItemBounds.getWidth() - outrect.getWidth();
}
if (outrect.getHeight() < treeItemBounds.getHeight()) {
outrect.Ymax += treeItemBounds.getHeight() - outrect.getHeight();
}
}
int width = outrect.getWidth();
int height = outrect.getHeight();
sos2.writeRECT(outrect);
sos2.writeFIXED8(frameRate);
sos2.writeUI16(frameCount); //framecnt
FileAttributesTag fa = swf.getFileAttributes();
if (fa != null) {
fa.writeTag(sos2);
}
SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor();
if (setBgColorTag == null) {
setBgColorTag = new SetBackgroundColorTag(swf, new RGB(backgroundColor));
}
setBgColorTag.writeTag(sos2);
if (treeItem instanceof Frame) {
Frame fn = (Frame) treeItem;
Timelined parent = fn.timeline.timelined;
Set<Integer> doneCharacters = new HashSet<>();
for (Tag t : parent.getTags()) {
if (t instanceof FileAttributesTag || t instanceof SetBackgroundColorTag) {
continue;
}
if (t instanceof DoActionTag || t instanceof DoInitActionTag) {
// todo: Maybe DoABC tags should be removed, too
continue;
}
Set<Integer> needed = new HashSet<>();
t.getNeededCharactersDeep(needed);
for (int n : needed) {
if (!doneCharacters.contains(n)) {
writeTag(swf.getCharacter(n), sos2);
doneCharacters.add(n);
}
}
//if (t instanceof ShowFrameTag || t instanceof PlaceObjectTypeTag || t instanceof RemoveTag) {
// continue;
//}
if (t instanceof CharacterTag) {
int characterId = ((CharacterTag) t).getCharacterId();
doneCharacters.add(characterId);
writeTag(t, sos2);
}
}
RECT r = parent.getRect();
for (Map.Entry<Integer, DepthState> value : fn.layers.entrySet()) {
PlaceObjectTypeTag pot = value.getValue().toPlaceObjectTag(value.getKey());
MATRIX mat = new MATRIX(pot.getMatrix());
mat.translateX += width / 2 - r.getWidth() / 2;
mat.translateY += height / 2 - r.getHeight() / 2;
pot.setMatrix(mat);
pot.writeTag(sos2);
}
new ShowFrameTag(swf).writeTag(sos2);
} else {
boolean isSprite = false;
if (treeItem instanceof DefineSpriteTag) {
isSprite = true;
}
int chtId = -1;
if (treeItem instanceof CharacterTag) {
chtId = ((CharacterTag) treeItem).getCharacterId();
}
if (treeItem instanceof DefineBitsTag) {
JPEGTablesTag jtt = swf.getJtt();
if (jtt != null) {
jtt.writeTag(sos2);
}
} else if (treeItem instanceof AloneTag) {
} else {
Set<Integer> needed = new HashSet<>();
((Tag) treeItem).getNeededCharactersDeep(needed);
for (int n : needed) {
if (isSprite && chtId == n) {
continue;
}
CharacterTag characterTag = swf.getCharacter(n);
if (characterTag instanceof DefineBitsTag) {
JPEGTablesTag jtt = swf.getJtt();
if (jtt != null) {
jtt.writeTag(sos2);
}
}
writeTag(characterTag, sos2);
}
}
writeTag((Tag) treeItem, sos2);
MATRIX mat = new MATRIX();
mat.hasRotate = false;
mat.hasScale = false;
mat.translateX = 0;
mat.translateY = 0;
if (treeItem instanceof BoundedTag) {
RECT r = ((BoundedTag) treeItem).getRect();
mat.translateX = -r.Xmin;
mat.translateY = -r.Ymin;
mat.translateX = mat.translateX + width / 2 - r.getWidth() / 2;
mat.translateY = mat.translateY + height / 2 - r.getHeight() / 2;
} else {
mat.translateX = width / 4;
mat.translateY = height / 4;
}
if (treeItem instanceof FontTag) {
FontTag ft = (FontTag) classicTag((Tag) treeItem);
int countGlyphsTotal = ft.getGlyphShapeTable().size();
int countGlyphs = Math.min(SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW, countGlyphsTotal);
int fontId = ft.getFontId();
int cols = (int) Math.ceil(Math.sqrt(countGlyphs));
int rows = (int) Math.ceil(((float) countGlyphs) / ((float) cols));
if (rows == 0) {
rows = 1;
cols = 1;
}
int x = 0;
int y = 0;
int firstGlyphIndex = fontPageNum * SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW;
countGlyphs = Math.min(SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW, countGlyphsTotal - firstGlyphIndex);
List<SHAPE> shapes = ft.getGlyphShapeTable();
int maxw = 0;
for (int f = firstGlyphIndex; f < firstGlyphIndex + countGlyphs; f++) {
RECT b = shapes.get(f).getBounds();
if (b.Xmin == Integer.MAX_VALUE) {
continue;
}
if (b.Ymin == Integer.MAX_VALUE) {
continue;
}
int w = (int) (b.getWidth() / ft.getDivider());
if (w > maxw) {
maxw = w;
}
x++;
}
x = 0;
int BORDER = 3 * 20;
int textHeight = height / rows;
while (maxw * textHeight / 1024.0 > width / cols - 2 * BORDER) {
textHeight--;
}
MATRIX tmat = new MATRIX();
for (int f = firstGlyphIndex; f < firstGlyphIndex + countGlyphs; f++) {
if (x >= cols) {
x = 0;
y++;
}
List<TEXTRECORD> rec = new ArrayList<>();
TEXTRECORD tr = new TEXTRECORD();
RECT b = shapes.get(f).getBounds();
int xmin = b.Xmin == Integer.MAX_VALUE ? 0 : (int) (b.Xmin / ft.getDivider());
xmin *= textHeight / 1024.0;
int ymin = b.Ymin == Integer.MAX_VALUE ? 0 : (int) (b.Ymin / ft.getDivider());
ymin *= textHeight / 1024.0;
int w = (int) (b.getWidth() / ft.getDivider());
w *= textHeight / 1024.0;
int h = (int) (b.getHeight() / ft.getDivider());
h *= textHeight / 1024.0;
tr.fontId = fontId;
tr.styleFlagsHasFont = true;
tr.textHeight = textHeight;
tr.xOffset = -xmin;
tr.yOffset = 0;
tr.styleFlagsHasXOffset = true;
tr.styleFlagsHasYOffset = true;
tr.glyphEntries = new ArrayList<>(1);
tr.styleFlagsHasColor = true;
tr.textColor = new RGB(0, 0, 0);
GLYPHENTRY ge = new GLYPHENTRY();
double ga = ft.getGlyphAdvance(f);
int cw = ga == -1 ? w : (int) (ga / ft.getDivider() * textHeight / 1024.0);
ge.glyphAdvance = 0;
ge.glyphIndex = f;
tr.glyphEntries.add(ge);
rec.add(tr);
tmat.translateX = x * width / cols + width / cols / 2 - w / 2;
tmat.translateY = y * height / rows + height / rows / 2;
new DefineTextTag(swf, 999 + f, new RECT(0, cw, ymin, ymin + h), new MATRIX(), rec).writeTag(sos2);
new PlaceObject2Tag(swf, false, 1 + f, 999 + f, tmat, null, 0, null, -1, null).writeTag(sos2);
x++;
}
new ShowFrameTag(swf).writeTag(sos2);
} else if ((treeItem instanceof DefineMorphShapeTag) || (treeItem instanceof DefineMorphShape2Tag)) {
new PlaceObject2Tag(swf, false, 1, chtId, mat, null, 0, null, -1, null).writeTag(sos2);
new ShowFrameTag(swf).writeTag(sos2);
for (int ratio = 0; ratio < 65536; ratio += 65536 / frameCount) {
new PlaceObject2Tag(swf, true, 1, chtId, mat, null, ratio, null, -1, null).writeTag(sos2);
new ShowFrameTag(swf).writeTag(sos2);
}
} else if (treeItem instanceof SoundStreamHeadTypeTag) {
for (SoundStreamBlockTag blk : soundFrames) {
blk.writeTag(sos2);
new ShowFrameTag(swf).writeTag(sos2);
}
} else if (treeItem instanceof DefineSoundTag) {
ExportAssetsTag ea = new ExportAssetsTag(swf);
DefineSoundTag ds = (DefineSoundTag) treeItem;
ea.tags.add(ds.soundId);
ea.names.add("my_define_sound");
ea.writeTag(sos2);
List<Action> actions;
DoActionTag doa;
doa = new DoActionTag(swf, null);
actions = ASMParser.parse(0, false,
"ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\"\n"
+ "Push \"_root\"\n"
+ "GetVariable\n"
+ "Push \"my_sound\" 0.0 \"Sound\"\n"
+ "NewObject\n"
+ "SetMember\n"
+ "Push \"my_define_sound\" 1 \"_root\"\n"
+ "GetVariable\n"
+ "Push \"my_sound\"\n"
+ "GetMember\n"
+ "Push \"attachSound\"\n"
+ "CallMethod\n"
+ "Pop\n"
+ "Stop", swf.version, false);
doa.setActions(actions);
doa.writeTag(sos2);
new ShowFrameTag(swf).writeTag(sos2);
actions = ASMParser.parse(0, false,
"ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\" \"start\"\n"
+ "StopSounds\n"
+ "Push \"_root\"\n"
+ "GetVariable\n"
+ "Push \"my_sound\" 0.0 \"Sound\"\n"
+ "NewObject\n"
+ "SetMember\n"
+ "Push \"my_define_sound\" 1 \"_root\"\n"
+ "GetVariable\n"
+ "Push \"my_sound\"\n"
+ "GetMember\n"
+ "Push \"attachSound\"\n"
+ "CallMethod\n"
+ "Pop\n"
+ "Push 9999 0.0 2 \"_root\"\n"
+ "GetVariable\n"
+ "Push \"my_sound\"\n"
+ "GetMember\n"
+ "Push \"start\"\n"
+ "CallMethod\n"
+ "Pop\n"
+ "Stop", swf.version, false);
doa.setActions(actions);
doa.writeTag(sos2);
new ShowFrameTag(swf).writeTag(sos2);
actions = ASMParser.parse(0, false,
"ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\" \"onSoundComplete\" \"start\" \"execParam\"\n"
+ "StopSounds\n"
+ "Push \"_root\"\n"
+ "GetVariable\n"
+ "Push \"my_sound\" 0.0 \"Sound\"\n"
+ "NewObject\n"
+ "SetMember\n"
+ "Push \"my_define_sound\" 1 \"_root\"\n"
+ "GetVariable\n"
+ "Push \"my_sound\"\n"
+ "GetMember\n"
+ "Push \"attachSound\"\n"
+ "CallMethod\n"
+ "Pop\n"
+ "Push \"_root\"\n"
+ "GetVariable\n"
+ "Push \"my_sound\"\n"
+ "GetMember\n"
+ "Push \"onSoundComplete\"\n"
+ "DefineFunction2 \"\" 0 2 false true true false true false true false false {\n"
+ "Push 0.0 register1 \"my_sound\"\n"
+ "GetMember\n"
+ "Push \"start\"\n"
+ "CallMethod\n"
+ "Pop\n"
+ "}\n"
+ "SetMember\n"
+ "Push \"_root\"\n"
+ "GetVariable\n"
+ "Push \"execParam\"\n"
+ "GetMember\n"
+ "Push 1 \"_root\"\n"
+ "GetVariable\n"
+ "Push \"my_sound\"\n"
+ "GetMember\n"
+ "Push \"start\"\n"
+ "CallMethod\n"
+ "Pop\n"
+ "Stop", swf.version, false);
doa.setActions(actions);
doa.writeTag(sos2);
new ShowFrameTag(swf).writeTag(sos2);
actions = ASMParser.parse(0, false,
"StopSounds\n"
+ "Stop", swf.version, false);
doa.setActions(actions);
doa.writeTag(sos2);
new ShowFrameTag(swf).writeTag(sos2);
new ShowFrameTag(swf).writeTag(sos2);
} else if (treeItem instanceof DefineVideoStreamTag) {
new PlaceObject2Tag(swf, false, 1, chtId, mat, null, -1, null, -1, null).writeTag(sos2);
List<VideoFrameTag> frs = new ArrayList<>(videoFrames.values());
Collections.sort(frs, new Comparator<VideoFrameTag>() {
@Override
public int compare(VideoFrameTag o1, VideoFrameTag o2) {
return o1.frameNum - o2.frameNum;
}
});
boolean first = true;
int ratio = 0;
for (VideoFrameTag f : frs) {
if (!first) {
ratio++;
new PlaceObject2Tag(swf, true, 1, -1, null, null, ratio, null, -1, null).writeTag(sos2);
}
f.writeTag(sos2);
new ShowFrameTag(swf).writeTag(sos2);
first = false;
}
} else if (treeItem instanceof DefineSpriteTag) {
DefineSpriteTag s = (DefineSpriteTag) treeItem;
Tag lastTag = null;
for (Tag t : s.getTags()) {
if (t instanceof EndTag) {
break;
} else if (t instanceof PlaceObjectTypeTag) {
PlaceObjectTypeTag pt = (PlaceObjectTypeTag) t;
MATRIX m = pt.getMatrix();
MATRIX m2 = new Matrix(m).preConcatenate(new Matrix(mat)).toMATRIX();
pt.writeTagWithMatrix(sos2, m2);
lastTag = t;
} else {
t.writeTag(sos2);
lastTag = t;
}
}
if (!s.getTags().isEmpty() && (lastTag != null) && (!(lastTag instanceof ShowFrameTag))) {
new ShowFrameTag(swf).writeTag(sos2);
}
} else {
new PlaceObject2Tag(swf, false, 1, chtId, mat, null, 0, null, -1, null).writeTag(sos2);
new ShowFrameTag(swf).writeTag(sos2);
}
} // not showframe
new EndTag(swf).writeTag(sos2);
data = baos.toByteArray();
}
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(tempFile))) {
SWFOutputStream sos = new SWFOutputStream(fos, Math.max(10, swf.version));
sos.write("FWS".getBytes());
sos.write(swf.version);
sos.writeUI32(sos.getPos() + data.length + 4);
sos.write(data);
fos.flush();
}
if (flashPanel != null) {
flashPanel.displaySWF(tempFile.getAbsolutePath(), backgroundColor, frameRate);
}
showFlashViewerPanel();
} catch (IOException | ActionParseException ex) {
Logger.getLogger(PreviewPanel.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void showSwf(SWF swf) {
Color backgroundColor = View.getDefaultBackgroundColor();
SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor();
if (setBgColorTag != null) {
backgroundColor = setBgColorTag.backgroundColor.toColor();
}
if (tempFile != null) {
tempFile.delete();
}
try {
tempFile = File.createTempFile("ffdec_view_", ".swf");
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(tempFile))) {
swf.saveTo(fos, false);
}
//Inject Loader
if (swf.isAS3() && Configuration.autoOpenLoadedSWFs.get() && !Configuration.internalFlashViewer.get() && !DebuggerTools.hasDebugger(swf)) {
SWF instrSWF;
try (InputStream fis = new BufferedInputStream(new FileInputStream(tempFile))) {
instrSWF = new SWF(fis, false, false);
}
DebuggerTools.switchDebugger(instrSWF);
DebuggerTools.injectDebugLoader(instrSWF);
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(tempFile))) {
instrSWF.saveTo(fos);
}
}
flashPanel.displaySWF(tempFile.getAbsolutePath(), backgroundColor, swf.frameRate);
} catch (IOException iex) {
Logger.getLogger(PreviewPanel.class.getName()).log(Level.SEVERE, "Cannot create tempfile", iex);
} catch (InterruptedException ex) {
}
}
private void editMetadataButtonActionPerformed(ActionEvent evt) {
TreeItem item = mainPanel.tagTree.getCurrentTreeItem();
if (item == null) {
return;
}
if (item instanceof MetadataTag) {
metadataEditor.setEditable(true);
updateMetadataButtonsVisibility();
}
}
private void saveMetadataButtonActionPerformed(ActionEvent evt) {
metadataTag.xmlMetadata = metadataEditor.getText().replaceAll(">\r?\n<", "> <");
metadataTag.setModified(true);
metadataEditor.setEditable(Configuration.editorMode.get());
setMetadataModified(false);
updateMetadataButtonsVisibility();
mainPanel.repaintTree();
}
private void cancelMetadataButtonActionPerformed(ActionEvent evt) {
metadataEditor.setEditable(false);
metadataEditor.setText(formatMetadata(metadataTag.xmlMetadata, 4));
metadataEditor.setEditable(Configuration.editorMode.get());
setMetadataModified(false);
updateMetadataButtonsVisibility();
}
private void editGenericTagButtonActionPerformed(ActionEvent evt) {
TreeItem item = mainPanel.tagTree.getCurrentTreeItem();
if (item == null) {
return;
}
if (item instanceof TagScript) {
item = ((TagScript) item).getTag();
}
if (item instanceof Tag) {
genericEditButton.setVisible(false);
genericSaveButton.setVisible(true);
genericCancelButton.setVisible(true);
genericTagPanel.setEditMode(true, (Tag) item);
}
}
private void saveGenericTagButtonActionPerformed(ActionEvent evt) {
if (genericTagPanel.save()) {
Tag tag = genericTagPanel.getTag();
SWF swf = tag.getSwf();
swf.clearImageCache();
swf.updateCharacters();
tag.getTimelined().resetTimeline();
swf.assignClassesToSymbols();
swf.assignExportNamesToSymbols();
mainPanel.refreshTree(swf);
mainPanel.setTagTreeSelectedNode(tag);
genericEditButton.setVisible(true);
genericSaveButton.setVisible(false);
genericCancelButton.setVisible(false);
genericTagPanel.setEditMode(false, null);
}
}
private void cancelGenericTagButtonActionPerformed(ActionEvent evt) {
genericEditButton.setVisible(true);
genericSaveButton.setVisible(false);
genericCancelButton.setVisible(false);
genericTagPanel.setEditMode(false, null);
}
private void prevFontsButtonActionPerformed(ActionEvent evt) {
FontTag fontTag = fontPanel.getFontTag();
int pageCount = getFontPageCount(fontTag);
fontPageNum = (fontPageNum + pageCount - 1) % pageCount;
if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) {
imagePanel.setTimelined(MainPanel.makeTimelined(fontTag, fontPageNum), fontTag.getSwf(), 0);
}
}
private void nextFontsButtonActionPerformed(ActionEvent evt) {
FontTag fontTag = fontPanel.getFontTag();
int pageCount = getFontPageCount(fontTag);
fontPageNum = (fontPageNum + 1) % pageCount;
if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) {
imagePanel.setTimelined(MainPanel.makeTimelined(fontTag, fontPageNum), fontTag.getSwf(), 0);
}
}
@Override
public boolean tryAutoSave() {
// todo: implement
return textPanel.tryAutoSave() && false;
}
@Override
public boolean isEditing() {
return textPanel.isEditing()
|| (genericSaveButton.isVisible() && genericSaveButton.isEnabled())
|| (metadataSaveButton.isVisible() && metadataSaveButton.isEnabled());
}
}