diff --git a/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java b/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java index 7f47ade65..f2d37205b 100644 --- a/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/GenericTagPanel.java @@ -1,596 +1,603 @@ -/* - * 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*/); - } -} +/* + * 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.BinaryDataEditor; +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.ByteArrayRange; +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()); + + protected final MainPanel mainPanel; + + 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(MainPanel mainPanel) { + super(new BorderLayout()); + + this.mainPanel = mainPanel; + 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 (type.equals(ByteArrayRange.class)) { + editor = new BinaryDataEditor(mainPanel, 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 333994e84..b2737f349 100644 --- a/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.gui.generictageditors.BinaryDataEditor; import com.jpexs.decompiler.flash.gui.generictageditors.BooleanEditor; import com.jpexs.decompiler.flash.gui.generictageditors.ColorEditor; import com.jpexs.decompiler.flash.gui.generictageditors.GenericTagEditor; @@ -39,7 +40,6 @@ import com.jpexs.decompiler.flash.types.annotations.parser.AnnotationParseExcept import com.jpexs.decompiler.flash.types.annotations.parser.ConditionEvaluator; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.ConcreteClasses; -import com.jpexs.helpers.Helper; import com.jpexs.helpers.ReflectionTools; import java.awt.BorderLayout; import java.awt.Color; @@ -51,7 +51,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.io.File; import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.Field; @@ -92,8 +91,6 @@ public class GenericTagTreePanel extends GenericTagPanel { private static final Logger logger = Logger.getLogger(GenericTagTreePanel.class.getName()); - private final MainPanel mainPanel; - private JTree tree; private Tag editedTag; @@ -166,6 +163,8 @@ public class GenericTagTreePanel extends GenericTagPanel { editor = new StringEditor(field.getName(), obj, field, index, type, multiline != null); } else if (type.equals(RGB.class) || type.equals(RGBA.class) || type.equals(ARGB.class)) { editor = new ColorEditor(field.getName(), obj, field, index, type); + } else if (type.equals(ByteArrayRange.class)) { + editor = new BinaryDataEditor(mainPanel, field.getName(), obj, field, index, type); } if (editor != null) { if (editors == null) { @@ -195,10 +194,6 @@ public class GenericTagTreePanel extends GenericTagPanel { pan.add(nameLabel); JComponent editorComponent = (JComponent) editor; - if (editorComponent == null && type.equals(ByteArrayRange.class)) { - //ByteArrayRange bar = (ByteArrayRange) field.get(obj); - nameLabel.setText(nameLabel.getText() + " ..."); - } if (editorComponent != null) { nameLabel.setSize(nameLabel.getWidth(), editorComponent.getHeight()); editorComponent.setAlignmentY(TOP_ALIGNMENT); @@ -269,8 +264,8 @@ public class GenericTagTreePanel extends GenericTagPanel { } public GenericTagTreePanel(MainPanel mainPanel) { + super(mainPanel); setLayout(new BorderLayout()); - this.mainPanel = mainPanel; tree = new MyTree(); add(new JScrollPane(tree), BorderLayout.CENTER); @@ -289,18 +284,7 @@ public class GenericTagTreePanel extends GenericTagPanel { if (selObject instanceof FieldNode) { final FieldNode fnode = (FieldNode) selObject; Field field = fnode.fieldSet.get(FIELD_INDEX); - if (field.getType().equals(ByteArrayRange.class)) { - File selectedFile = mainPanel.showImportFileChooser(""); - if (selectedFile != null) { - File selfile = Helper.fixDialogFile(selectedFile); - byte[] data = Helper.readFile(selfile.getAbsolutePath()); - try { - field.set(fnode.obj, new ByteArrayRange(data)); - } catch (IllegalArgumentException | IllegalAccessException ex) { - Logger.getLogger(GenericTagTreePanel.class.getName()).log(Level.SEVERE, null, ex); - } - } - } else if (ReflectionTools.needsIndex(field)) { + if (ReflectionTools.needsIndex(field)) { SWFArray swfArray = fnode.fieldSet.get(FIELD_INDEX).getAnnotation(SWFArray.class); String itemStr = ""; diff --git a/src/com/jpexs/decompiler/flash/gui/generictageditors/BinaryDataEditor.java b/src/com/jpexs/decompiler/flash/gui/generictageditors/BinaryDataEditor.java new file mode 100644 index 000000000..1b751b2a2 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/generictageditors/BinaryDataEditor.java @@ -0,0 +1,131 @@ +/* + * 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.generictageditors; + +import com.jpexs.decompiler.flash.gui.AppStrings; +import com.jpexs.decompiler.flash.gui.MainPanel; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.ReflectionTools; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.lang.reflect.Field; +import javax.swing.JButton; + +/** + * + * @author JPEXS + */ +public class BinaryDataEditor extends JButton implements GenericTagEditor { + + private final MainPanel mainPanel; + + private final Object obj; + + private final Field field; + + private final int index; + + private final Class type; + + private final String fieldName; + + private Object value; + + public BinaryDataEditor(MainPanel mainPanel, String fieldName, Object obj, Field field, int index, Class type) { + super(); + this.mainPanel = mainPanel; + this.obj = obj; + this.field = field; + this.index = index; + this.type = type; + this.fieldName = fieldName; + setText(AppStrings.translate("button.replace")); + addActionListener(this::buttonActionPerformed); + + try { + ByteArrayRange bar = (ByteArrayRange) ReflectionTools.getValue(obj, field, index); + setToolTipText(bar.getLength() + " bytes"); + value = bar; + } catch (IllegalArgumentException | IllegalAccessException ex) { + // ignore + } + } + + private void buttonActionPerformed(ActionEvent evt) { + File selectedFile = mainPanel.showImportFileChooser(""); + if (selectedFile != null) { + File selfile = Helper.fixDialogFile(selectedFile); + byte[] data = Helper.readFile(selfile.getAbsolutePath()); + setToolTipText(data.length + " bytes"); + value = new ByteArrayRange(data); + } + } + + @Override + public void save() { + try { + ReflectionTools.setValue(obj, field, index, value); + } catch (IllegalArgumentException | IllegalAccessException ex) { + // ignore + } + } + + @Override + public void addChangeListener(final ChangeListener l) { + final GenericTagEditor t = this; + addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + l.change(t); + } + }); + } + + @Override + public Object getChangedValue() { + return value; + } + + @Override + public String getFieldName() { + return fieldName; + } + + @Override + public Field getField() { + return field; + } + + @Override + public String getReadOnlyValue() { + return getChangedValue().toString(); + } + + @Override + public BaselineResizeBehavior getBaselineResizeBehavior() { + return Component.BaselineResizeBehavior.CONSTANT_ASCENT; + } + + @Override + public int getBaseline(int width, int height) { + return 0; + } +}