diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a71b9c8..b2cb772e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. - [#1743] GFX - Adding DefineExternalImage2 and DefineSubImage tags - [#1822], [#1803] AS3 direct editation - optional using AIR (airglobal.swc) to compile - [#1501] Bulk import shapes +- [#1680] Pinning objects ### Fixed - [#1869] Replace references now replaces all references, not just PlaceObject @@ -34,9 +35,11 @@ All notable changes to this project will be documented in this file. - DefineShape4 SVG import NullPointerException - List of objects under cursor and coordinates not showing - ConcurrentModificationException in getCharacters on exit +- Header of display panel not visible on certain color schemes ### Changed - GFX - DefineExternalImage2 no longer handled as character +- Raw editor does not show tag name in the tree (it's now in the new pinnable head) ## [16.3.1] - 2022-11-14 ### Fixed @@ -2616,6 +2619,7 @@ All notable changes to this project will be documented in this file. [#1822]: https://www.free-decompiler.com/flash/issues/1822 [#1803]: https://www.free-decompiler.com/flash/issues/1803 [#1501]: https://www.free-decompiler.com/flash/issues/1501 +[#1680]: https://www.free-decompiler.com/flash/issues/1680 [#1869]: https://www.free-decompiler.com/flash/issues/1869 [#1872]: https://www.free-decompiler.com/flash/issues/1872 [#1692]: https://www.free-decompiler.com/flash/issues/1692 diff --git a/README.md b/README.md index e2ce89746..e185d2600 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ And links also these libraries: * [flashdebugger library] (Debugging ActionScript) - LGPLv3 * FFDec Library (LGPLv3) - see below -Application uses also some icons of the [Silk icons pack], [Silk companion 1] and [FatCow icons pack]. +Application uses also some icons of the [Silk icons pack], [Silk companion 1], [FatCow icons pack] and [Aha-Soft icons pack]. ### Library FFDec Library is licensed under GNU LGPL v3 (LGPL-3.0-or-later), see [license.txt](libsrc/ffdec_lib/license.txt) for details. @@ -193,6 +193,7 @@ And also links to these libraries: [Silk icons pack]: http://www.famfamfam.com/lab/icons/silk/ [Silk companion 1]: http://damieng.com/creative/icons/silk-companion-1-icons [FatCow icons pack]: http://www.fatcow.com/free-icons +[Aha-Soft icons pack]: http://www.aha-soft.com [sfntly]: https://code.google.com/p/sfntly/ [JLayer]: http://www.javazoom.net/javalayer/javalayer.html [Animated GIF Writer]: http://elliot.kroo.net/software/java/GifSequenceWriter/ diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index 7ff607c37..f90a390d8 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -802,6 +802,12 @@ public final class Configuration { @ConfigurationCategory("ui") public static ConfigurationItem showImportShapeInfo = null; + @ConfigurationDefaultString("") + public static ConfigurationItem pinnedItemsTagTreePaths = null; + + @ConfigurationDefaultString("") + public static ConfigurationItem pinnedItemsTagListPaths = null; + private enum OSId { WINDOWS, OSX, UNIX } diff --git a/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java b/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java index 203062964..5c8ac00d7 100644 --- a/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java @@ -434,7 +434,8 @@ public class GenericTagTreePanel extends GenericTagPanel { super(mainPanel); setLayout(new BorderLayout()); tree = new MyTree(); - + tree.setRootVisible(false); + tree.setShowsRootHandles(true); add(new FasterScrollPane(tree), BorderLayout.CENTER); tree.addMouseListener(new MouseAdapter() { @Override @@ -643,9 +644,10 @@ public class GenericTagTreePanel extends GenericTagPanel { if (component instanceof JLabel) { JLabel lab = (JLabel) component; if (value == tree.getModel().getRoot()) { + //It still does not matter since root is hidden if (editedTag != null) { lab.setIcon(AbstractTagTree.getIconForType(AbstractTagTree.getTreeNodeType(editedTag))); - } + } } } return component; diff --git a/src/com/jpexs/decompiler/flash/gui/HeaderLabel.java b/src/com/jpexs/decompiler/flash/gui/HeaderLabel.java index 4d166ce14..c217c09eb 100644 --- a/src/com/jpexs/decompiler/flash/gui/HeaderLabel.java +++ b/src/com/jpexs/decompiler/flash/gui/HeaderLabel.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.configuration.Configuration; +import java.awt.Color; import java.awt.Graphics; import java.awt.SystemColor; import java.awt.geom.GeneralPath; @@ -69,11 +70,14 @@ public class HeaderLabel extends JLabel { @Override public void paint(Graphics g) { + Color foregroundColor; if (Configuration.useRibbonInterface.get()) { SubstanceSkin skin = SubstanceLookAndFeel.getCurrentSkin(); g.setColor(skin.getColorScheme(DecorationAreaType.HEADER, ColorSchemeAssociationKind.FILL, ComponentState.ENABLED).getBackgroundFillColor()); + foregroundColor = skin.getColorScheme(DecorationAreaType.HEADER, ColorSchemeAssociationKind.FILL, ComponentState.ENABLED).getForegroundColor(); } else { g.setColor(SystemColor.control); + foregroundColor = SystemColor.controlText; } g.fillRect(0, 0, getWidth(), getHeight()); if (Configuration.useRibbonInterface.get()) { @@ -94,13 +98,11 @@ public class HeaderLabel extends JLabel { SubstanceSkin skin = SubstanceLookAndFeel.getCurrentSkin(); borderPainter.paintBorder(g, this, getWidth(), getHeight() + dy, contour, contourInner, skin.getColorScheme(DecorationAreaType.HEADER, ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED)); - g.setColor(skin.getColorScheme(DecorationAreaType.HEADER, ColorSchemeAssociationKind.FILL, ComponentState.ENABLED).getForegroundColor()); - } else { - g.setColor(SystemColor.controlText); } JLabel lab = new JLabel(getText(), JLabel.CENTER); lab.setSize(getSize()); + lab.setForeground(foregroundColor); lab.paint(g); } } diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index 501c6f48a..3651d2417 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -2559,7 +2559,7 @@ public class Main { searchResultsStorage.save(); } catch (IOException ex) { //ignore - } + } Configuration.saveConfig(); if (mainFrame != null && mainFrame.getPanel() != null) { mainFrame.getPanel().unloadFlashPlayer(); diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index 2f6042ff4..f892e7d3f 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -255,8 +255,8 @@ public abstract class MainFrameMenu implements MenuBuilder { } if (swf != null) { - boolean result = Main.closeAll(); - if (result) { + boolean result = Main.closeAll(); + if (result) { swf = null; Timer timer = new Timer(); timer.schedule(new TimerTask() { diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 1e64df7fb..074cad4da 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -100,6 +100,7 @@ import com.jpexs.decompiler.flash.gui.tagtree.AbstractTagTreeModel; import com.jpexs.decompiler.flash.gui.tagtree.TagTree; import com.jpexs.decompiler.flash.gui.tagtree.TagTreeContextMenu; import com.jpexs.decompiler.flash.gui.tagtree.TagTreeModel; +import com.jpexs.decompiler.flash.gui.tagtree.TreeRoot; import com.jpexs.decompiler.flash.gui.timeline.TimelineViewPanel; import com.jpexs.decompiler.flash.helpers.FileTextWriter; import com.jpexs.decompiler.flash.helpers.Freed; @@ -250,6 +251,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; +import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.Icon; @@ -265,6 +267,10 @@ import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.SwingConstants; import javax.swing.UIManager; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.TreeSelectionEvent; @@ -398,10 +404,20 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se private CalculateMissingNeededThread calculateMissingNeededThread; private List> orderedClipboard = new ArrayList<>(); - private Map clipboard = new WeakHashMap<>(); + private Map clipboard = new WeakHashMap<>(); private boolean clipboardCut = false; + + private PinsPanel pinsPanel; + public void savePins() { + pinsPanel.save(); + } + + public void clearPins() { + pinsPanel.clear(); + } + private void handleTreeKeyReleased(KeyEvent e) { AbstractTagTree tree = (AbstractTagTree) e.getSource(); if ((e.getKeyCode() == KeyEvent.VK_UP @@ -923,7 +939,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se statusPanel = new MainFrameStatusPanel(this); add(statusPanel, BorderLayout.SOUTH); - + displayPanel = new JPanel(new CardLayout()); DefaultSyntaxKit.initKit(); @@ -986,9 +1002,20 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } }); + JPanel rightPanel = new JPanel(new BorderLayout()); + rightPanel.add(displayPanel, BorderLayout.CENTER); + pinsPanel = new PinsPanel(this); + pinsPanel.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + setTagTreeSelectedNode(getCurrentTree(), pinsPanel.getCurrent()); + } + }); + rightPanel.add(pinsPanel, BorderLayout.NORTH); + //displayPanel.setBorder(BorderFactory.createLineBorder(Color.black)); splitPane2 = new JPersistentSplitPane(JSplitPane.VERTICAL_SPLIT, treePanel, detailPanel, Configuration.guiSplitPane2DividerLocationPercent); - splitPane1 = new JPersistentSplitPane(JSplitPane.HORIZONTAL_SPLIT, splitPane2, displayPanel, Configuration.guiSplitPane1DividerLocationPercent); + splitPane1 = new JPersistentSplitPane(JSplitPane.HORIZONTAL_SPLIT, splitPane2, rightPanel, Configuration.guiSplitPane1DividerLocationPercent); welcomePanel = createWelcomePanel(); add(welcomePanel, BorderLayout.CENTER); @@ -1147,6 +1174,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se enableDrop(true); calculateMissingNeededThread = new CalculateMissingNeededThread(); calculateMissingNeededThread.start(); + pinsPanel.load(); } public void closeTagTreeSearch() { @@ -1204,6 +1232,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se doFilter(); reload(false); View.expandTreeNodes(getCurrentTree(), expandedNodes); + pinsPanel.load(); } public ABCPanel getABCPanel() { @@ -1315,6 +1344,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se return false; } } + + clearPins(); List swfsLists = new ArrayList<>(swfs); @@ -1337,7 +1368,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } for (SWF swf : swfsToClose) { - swf.clearTagSwfs(); + swf.clearTagSwfs(); } refreshTree(); @@ -1385,6 +1416,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } for (SWF swf : swfsToClose) { Main.searchResultsStorage.destroySwf(swf); + pinsPanel.removeSwf(swf); } swfs.remove(swfList); @@ -4195,6 +4227,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se setTreeModel(view); switch (view) { case VIEW_DUMP: + pinsPanel.setVisible(false); currentView = view; Configuration.lastView.set(currentView); if (!isWelcomeScreen) { @@ -4206,6 +4239,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se reload(true); return true; case VIEW_RESOURCES: + pinsPanel.setVisible(true); currentView = view; Configuration.lastView.set(currentView); if (!isWelcomeScreen) { @@ -4225,6 +4259,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se reload(true); return true; case VIEW_TIMELINE: + pinsPanel.setVisible(false); currentView = view; Configuration.lastView.set(currentView); final SWF swf = getCurrentSwf(); @@ -4247,6 +4282,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } return false; case VIEW_TAGLIST: + pinsPanel.setVisible(true); currentView = view; Configuration.lastView.set(currentView); if (!isWelcomeScreen) { @@ -4574,11 +4610,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } else { showDetail(DETAILCARDEMPTYPANEL); } - showCard(CARDACTIONSCRIPT3PANEL); - return; - } - - if (treeItem instanceof Tag) { + showCard(CARDACTIONSCRIPT3PANEL); + } else if (treeItem instanceof Tag) { Tag tag = (Tag) treeItem; TagInfo tagInfo = new TagInfo(treeItem.getSwf()); tag.getTagInfo(tagInfo); @@ -4689,9 +4722,14 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } else if (treeItem instanceof BUTTONRECORD) { showPreview(treeItem, previewPanel, -1, null); showCard(CARDPREVIEWPANEL); - } else { + } else if (!(treeItem instanceof ScriptPack)){ showCard(CARDEMPTYPANEL); } + if (treeItem instanceof TreeRoot) { + pinsPanel.setCurrent(null); + } else { + pinsPanel.setCurrent(treeItem); + } } public void repaintTree() { diff --git a/src/com/jpexs/decompiler/flash/gui/PinButton.java b/src/com/jpexs/decompiler/flash/gui/PinButton.java new file mode 100644 index 000000000..25e90681c --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/PinButton.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2022 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.configuration.Configuration; +import com.jpexs.decompiler.flash.gui.tagtree.AbstractTagTree; +import com.jpexs.decompiler.flash.treeitems.TreeItem; +import com.sun.tools.javac.code.Types; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.SystemColor; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.basic.BasicPanelUI; +import org.pushingpixels.substance.api.ColorSchemeAssociationKind; +import org.pushingpixels.substance.api.ComponentState; +import org.pushingpixels.substance.api.DecorationAreaType; +import org.pushingpixels.substance.api.SubstanceLookAndFeel; +import org.pushingpixels.substance.api.SubstanceSkin; + +/** + * + * @author JPEXS + */ +public class PinButton extends JPanel { + + //private static final Border raisedBorder = BorderFactory.createCompoundBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED), BorderFactory.createEmptyBorder(3, 5, 0, 5)); + //private static final Border loweredBorder = BorderFactory.createCompoundBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED), BorderFactory.createEmptyBorder(3, 5, 0, 5)); + private List actionListeners = new ArrayList<>(); + private List changeListeners = new ArrayList<>(); + + private boolean pinned; + + private boolean mouseOverPin = false; + private boolean mouseOver = false; + + private JLabel button; + + private TreeItem item; + + private boolean selected; + + private Color color; + private Color hilightedColor; + private Color hilightedTextColor; + private Color selectedColor; + private Color selectedTextColor; + private Color borderColor; + private Color textColor; + + private JLabel label; + + public PinButton(TreeItem item, boolean pinned) { + //setBorder(raisedBorder); + setBorder(BorderFactory.createEmptyBorder(5, 10, 0, 10)); + this.item = item; + this.pinned = pinned; + + if (Configuration.useRibbonInterface.get()) { + SubstanceSkin skin = SubstanceLookAndFeel.getCurrentSkin(); + color = skin.getColorScheme(DecorationAreaType.GENERAL, ColorSchemeAssociationKind.FILL, ComponentState.ENABLED).getBackgroundFillColor(); + hilightedColor = skin.getColorScheme(DecorationAreaType.GENERAL, ColorSchemeAssociationKind.FILL, ComponentState.ROLLOVER_SELECTED).getBackgroundFillColor(); + borderColor = skin.getColorScheme(DecorationAreaType.GENERAL, ColorSchemeAssociationKind.BORDER, ComponentState.ROLLOVER_SELECTED).getUltraDarkColor(); + textColor = skin.getColorScheme(DecorationAreaType.GENERAL, ColorSchemeAssociationKind.FILL, ComponentState.ENABLED).getForegroundColor(); + hilightedTextColor = skin.getColorScheme(DecorationAreaType.GENERAL, ColorSchemeAssociationKind.FILL, ComponentState.ROLLOVER_SELECTED).getForegroundColor(); + } else { + color = SystemColor.control; + hilightedColor = SystemColor.textHighlight; + borderColor = SystemColor.controlShadow; + textColor = SystemColor.controlText; + hilightedTextColor = SystemColor.textHighlightText; + } + + Color color2 = Color.white; + selectedColor = new Color( + (color.getRed() + color2.getRed()) / 2, + (color.getGreen() + color2.getGreen()) / 2, + (color.getBlue() + color2.getBlue()) / 2 + ); + + Color color3 = Color.black; + selectedTextColor = new Color( + (textColor.getRed() + color3.getRed()) / 2, + (textColor.getGreen() + color3.getGreen()) / 2, + (textColor.getBlue() + color3.getBlue()) / 2 + ); + + + label = new JLabel(); + label.setIcon(AbstractTagTree.getIconFor(item)); + label.setText(item.toString()); + + button = new JLabel(); + button.setMinimumSize(new Dimension(10 + 16, 16)); + button.setPreferredSize(new Dimension(10 + 16, 16)); + + MouseAdapter adapter = new MouseAdapter() { + + @Override + public void mousePressed(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + //setBorder(loweredBorder); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + /*if (selected) { + setBorder(loweredBorder); + } else { + setBorder(raisedBorder); + }*/ + fireAction(); + } + } + + @Override + public void mouseExited(MouseEvent e) { + mouseOver = false; + updateIcon(); + if (selected) { + setBackground(selectedColor); + label.setForeground(selectedTextColor); + } else { + setBackground(color); + label.setForeground(textColor); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + mouseOver = true; + if (selected) { + setBackground(selectedColor); + label.setForeground(selectedTextColor); + } else { + setBackground(hilightedColor); + label.setForeground(hilightedTextColor); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + mouseOver = true; + updateIcon(); + } + + }; + addMouseListener(adapter); + addMouseMotionListener(adapter); + + button.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); + button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + if (pinned) { + button.setIcon(View.getIcon("pinned16")); + button.setToolTipText(AppStrings.translate("unpin")); + } else { + button.setToolTipText(AppStrings.translate("pin")); + } + MouseAdapter buttonAdapter = new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { + mouseOverPin = true; + mouseOver = true; + if (selected) { + setBackground(selectedColor); + label.setForeground(selectedTextColor); + } else { + setBackground(hilightedColor); + label.setForeground(hilightedTextColor); + } + updateIcon(); + } + + @Override + public void mouseExited(MouseEvent e) { + mouseOverPin = false; + mouseOver = false; + if (selected) { + setBackground(selectedColor); + label.setForeground(selectedTextColor); + } else { + setBackground(color); + label.setForeground(textColor); + } + updateIcon(); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + PinButton.this.pinned = !PinButton.this.pinned; + if (PinButton.this.pinned) { + button.setToolTipText(AppStrings.translate("unpin")); + } else { + button.setToolTipText(AppStrings.translate("pin")); + } + updateIcon(); + fireChange(); + } + } + + }; + button.addMouseListener(buttonAdapter); + button.addMouseMotionListener(buttonAdapter); + + setLayout(new BorderLayout()); + add(label, BorderLayout.CENTER); + add(button, BorderLayout.EAST); + } + + private void updateIcon() { + + if (pinned) { + button.setIcon(View.getIcon("pinned16")); + } else if (mouseOverPin) { + button.setIcon(View.getIcon(pinned ? "pinned16" : "pin16")); + } else if (mouseOver) { + button.setIcon(View.getIcon(pinned ? "pinned16" : "canpin16")); + } else { + button.setIcon(null); + } + } + + private void fireAction() { + ActionEvent ev = new ActionEvent(this, 0, ""); + for (ActionListener listener : actionListeners) { + listener.actionPerformed(ev); + } + } + + private void fireChange() { + ChangeEvent ev = new ChangeEvent(this); + for (ChangeListener listener : changeListeners) { + listener.stateChanged(ev); + } + } + + public void addActionListener(ActionListener listener) { + actionListeners.add(listener); + } + + public void removeActionListner(ActionListener listener) { + actionListeners.remove(listener); + } + + public void addChangeListener(ChangeListener listener) { + changeListeners.add(listener); + } + + public void removeChangeListener(ChangeListener listener) { + changeListeners.add(listener); + } + + public boolean isPinned() { + return pinned; + } + + public TreeItem getItem() { + return item; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + if (selected) { + setBackground(selectedColor); + label.setForeground(selectedTextColor); + } else if (!mouseOver) { + setBackground(color); + label.setForeground(textColor); + } + repaint(); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + g.setColor(borderColor); + g.drawLine(0, 0, getWidth() - 1, 0); + g.drawLine(0, 0, 0, getHeight() - 1); + g.drawLine(getWidth() - 1, 0, getWidth() - 1, getHeight() - 1); + g.drawLine(0, getHeight() -1, getWidth() - 1, getHeight() - 1); + if (selected) { + g.setColor(hilightedColor); + g.drawLine(0, 0, getWidth() - 1, 0); + g.drawLine(0, 1, getWidth() - 1, 1); + g.drawLine(0, 2, getWidth() - 1, 2); + } + } + +} diff --git a/src/com/jpexs/decompiler/flash/gui/PinsPanel.java b/src/com/jpexs/decompiler/flash/gui/PinsPanel.java new file mode 100644 index 000000000..40b7b21ad --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/PinsPanel.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2022 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.configuration.Configuration; +import com.jpexs.decompiler.flash.gui.tagtree.TreeRoot; +import com.jpexs.decompiler.flash.treeitems.TreeItem; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import javax.swing.BorderFactory; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.tree.TreePath; + +/** + * + * @author JPEXS + */ +public class PinsPanel extends JPanel { + + private List items = new ArrayList<>(); + private TreeItem current; + private MainPanel mainPanel; + private PinButton lastSelectedButton; + private PinButton currentUnpinnedButton; + private List buttons = new ArrayList<>(); + private List changeListeners = new ArrayList<>(); + + private List missingTagTreePaths = new ArrayList<>(); + private List missingTagListPaths = new ArrayList<>(); + + + private static final String PATHS_SEPARATOR = "{#sep#}"; + + public PinsPanel(MainPanel mainPanel) { + this.mainPanel = mainPanel; + setLayout(new WrapLayout(WrapLayout.LEFT, 5, 2)); + setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); + } + + public void clear() { + for (TreeItem item : items) { + String tagTreePath = mainPanel.tagTree.getItemPathString(item); + if (tagTreePath == null) { + tagTreePath = ""; + } + + String tagListPath = mainPanel.tagListTree.getItemPathString(item); + if (tagListPath == null) { + tagListPath = ""; + } + + missingTagTreePaths.add(tagTreePath); + missingTagListPaths.add(tagListPath); + } + items.clear(); + rebuild(); + save(); + } + + public void setCurrent(TreeItem item) { + if (lastSelectedButton != null) { + lastSelectedButton.setSelected(false); + } + for (int i = 0; i < items.size(); i++) { + if (items.get(i) == item) { + this.current = item; + buttons.get(i).setSelected(true); + lastSelectedButton = buttons.get(i); + if (currentUnpinnedButton != null) { + remove(currentUnpinnedButton); + revalidate(); + repaint(); + } + return; + } + } + if (this.current == item) { + return; + } + + this.current = item; + + rebuild(); + } + + private String getTreeItemPath(TreeItem item) { + TreePath path = mainPanel.getCurrentTree().getModel().getTreePath(item); + if (path == null) { + return ""; + } + StringBuilder pathString = new StringBuilder(); + for (int i = 1; i < path.getPathCount(); i++) { + if (pathString.length() > 0) { + pathString.append(" / "); + } + pathString.append(path.getPathComponent(i).toString()); + } + return pathString.toString(); + } + + private void rebuild() { + removeAll(); + buttons.clear(); + currentUnpinnedButton = null; + boolean currentPinned = false; + for (TreeItem item : items) { + PinButton pinButton = new PinButton(item, true); + pinButton.setToolTipText(getTreeItemPath(item)); + pinButton.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON3) { + JPopupMenu pinMenu = new JPopupMenu(); + JMenuItem unpinMenuItem = new JMenuItem(AppStrings.translate("contextmenu.unpin")); + unpinMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + items.remove(item); + rebuild(); + fireChange(); + } + }); + pinMenu.add(unpinMenuItem); + + JMenuItem unpinAllMenuItem = new JMenuItem(AppStrings.translate("contextmenu.unpin.all")); + unpinAllMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + items.clear(); + rebuild(); + fireChange(); + } + }); + if (items.size() > 1) { + pinMenu.add(unpinAllMenuItem); + } + + JMenuItem unpinOthersMenuItem = new JMenuItem(AppStrings.translate("contextmenu.unpin.others")); + unpinOthersMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + items.clear(); + items.add(item); + rebuild(); + fireChange(); + } + }); + if (items.size() > 1) { + pinMenu.add(unpinOthersMenuItem); + } + pinMenu.show(pinButton, e.getX(), e.getY()); + } + } + + }); + pinButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (!pinButton.isPinned()) { + items.remove(item); + rebuild(); + } + save(); + fireChange(); + } + }); + pinButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + current = pinButton.getItem(); + if (lastSelectedButton != null) { + lastSelectedButton.setSelected(false); + if (!lastSelectedButton.isPinned()) { + PinsPanel.this.remove(lastSelectedButton); + revalidate(); + repaint(); + } + } + lastSelectedButton = pinButton; + pinButton.setSelected(true); + fireChange(); + } + }); + add(pinButton); + buttons.add(pinButton); + if (item == current) { + currentPinned = true; + lastSelectedButton = pinButton; + pinButton.setSelected(true); + } + } + if (!currentPinned && current != null) { + currentUnpinnedButton = new PinButton(current, false); + lastSelectedButton = currentUnpinnedButton; + add(currentUnpinnedButton); + currentUnpinnedButton.setToolTipText(getTreeItemPath(current)); + currentUnpinnedButton.setSelected(true); + currentUnpinnedButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + items.add(current); + rebuild(); + save(); + fireChange(); + } + }); + } + revalidate(); + repaint(); + } + + public void addChangeListener(ChangeListener listener) { + changeListeners.add(listener); + } + + public void removeChangeListener(ChangeListener listener) { + changeListeners.add(listener); + } + + private void fireChange() { + ChangeEvent ev = new ChangeEvent(this); + for (ChangeListener listener : changeListeners) { + listener.stateChanged(ev); + } + } + + public List getPinnedItems() { + return new ArrayList<>(items); + } + + public TreeItem getCurrent() { + return current; + } + + public void setPinnedItems(List items) { + this.items = new ArrayList<>(items); + rebuild(); + } + + public void save() { + StringBuilder tagTreePathsBuilder = new StringBuilder(); + StringBuilder tagListPathsBuilder = new StringBuilder(); + boolean first = true; + + for (int i = 0; i < missingTagTreePaths.size(); i++) { + if (!first) { + tagTreePathsBuilder.append(PATHS_SEPARATOR); + tagListPathsBuilder.append(PATHS_SEPARATOR); + } + tagTreePathsBuilder.append(missingTagTreePaths.get(i)); + tagListPathsBuilder.append(missingTagListPaths.get(i)); + first = false; + } + + for (TreeItem item : items) { + String tagTreePath = mainPanel.tagTree.getItemPathString(item); + if (tagTreePath == null) { + tagTreePath = ""; + } + + String tagListPath = mainPanel.tagListTree.getItemPathString(item); + if (tagListPath == null) { + tagListPath = ""; + } + if (!first) { + tagTreePathsBuilder.append(PATHS_SEPARATOR); + tagListPathsBuilder.append(PATHS_SEPARATOR); + } + tagTreePathsBuilder.append(tagTreePath); + tagListPathsBuilder.append(tagListPath); + first = false; + } + + Configuration.pinnedItemsTagTreePaths.set(tagTreePathsBuilder.toString()); + Configuration.pinnedItemsTagListPaths.set(tagListPathsBuilder.toString()); + } + + public void load() { + + final String PATHS_END = "{finish}"; + + List missingTagTreePaths = new ArrayList<>(); + List missingTagListPaths = new ArrayList<>(); + String tagTreePathsCombined = Configuration.pinnedItemsTagTreePaths.get() + PATHS_SEPARATOR + PATHS_END; + String tagListPathsCombined = Configuration.pinnedItemsTagListPaths.get() + PATHS_SEPARATOR + PATHS_END; + String[] tagTreePaths = tagTreePathsCombined.split(Pattern.quote(PATHS_SEPARATOR)); + String[] tagListPaths = tagListPathsCombined.split(Pattern.quote(PATHS_SEPARATOR)); + if (tagTreePaths.length != tagListPaths.length) { + return; + } + List items = new ArrayList<>(); + for (int i = 0; i < tagTreePaths.length - 1; i++) { + String tagTreePath = tagTreePaths[i]; + String tagListPath = tagListPaths[i]; + TreeItem item = mainPanel.tagTree.getTreeItemFromPathString(tagTreePath); + if (item == null || (item instanceof TreeRoot)) { + item = mainPanel.tagListTree.getTreeItemFromPathString(tagListPath); + } + if (item != null && !(item instanceof TreeRoot)) { + items.add(item); + } else { + missingTagTreePaths.add(tagTreePath); + missingTagListPaths.add(tagListPath); + } + } + this.items = items; + this.missingTagTreePaths = missingTagTreePaths; + this.missingTagListPaths = missingTagListPaths; + rebuild(); + } + + public void removeSwf(SWF swf) { + for (int i = 0; i < items.size(); i++) { + TreeItem item = items.get(i); + SWF itemSwf = item.getSwf(); + if (itemSwf == swf || itemSwf == null) { + + String tagTreePath = mainPanel.tagTree.getItemPathString(item); + if (tagTreePath == null) { + tagTreePath = ""; + } + + String tagListPath = mainPanel.tagListTree.getItemPathString(item); + if (tagListPath == null) { + tagListPath = ""; + } + + missingTagTreePaths.add(tagTreePath); + missingTagListPaths.add(tagListPath); + + items.remove(i); + i--; + } + } + save(); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/canpin16.png b/src/com/jpexs/decompiler/flash/gui/graphics/canpin16.png new file mode 100644 index 000000000..cb075f4d2 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/canpin16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/pin16.png b/src/com/jpexs/decompiler/flash/gui/graphics/pin16.png new file mode 100644 index 000000000..6de3ca817 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/pin16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/pinned16.png b/src/com/jpexs/decompiler/flash/gui/graphics/pinned16.png new file mode 100644 index 000000000..11aeda427 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/pinned16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index 5689fbe6c..4e049b7e1 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -611,3 +611,9 @@ config.description.airLibLocation = Location of airglobal.swc AIR library. It ca config.name.showImportShapeInfo = Show information before importing shapes config.description.showImportShapeInfo = Displays some info about how importing shapes works after clicking Import shapes in the menu. + +config.name.pinnedItemsTagTreePaths = Pinned items paths in tag tree +config.description.pinnedItemsTagTreePaths = Paths of nodes of tag tree which are pinned. + +config.name.pinnedItemsTagListPaths = Pinned items paths in tag list view tree +config.description.pinnedItemsTagListPaths = Paths of nodes of tag list view tree which are pinned. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties index db751089b..37a0b9ad0 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties @@ -598,3 +598,9 @@ config.description.airLibLocation = Um\u00edst\u011bn\u00ed knihovny AIR s n\u00 config.name.showImportShapeInfo = Zobrazit informaci p\u0159ed importem tvar\u016f config.description.showImportShapeInfo = Zobraz\u00ed n\u011bjak\u00e9 informace o tom jak import tvar\u016f funguje po kliku na import tvar\u016f v menu. + +config.name.pinnedItemsTagTreePaths = Cesty p\u0159ipnut\u00fdch polo\u017eek v stromu tag\u016f +config.description.pinnedItemsTagTreePaths = Cesty uzl\u016f v stromu tag\u016f, kter\u00e9 jsou p\u0159ipnuty. + +config.name.pinnedItemsTagListPaths = Cesty p\u0159ipnut\u00fdch polo\u017eek v stromu seznamu tag\u016f +config.description.pinnedItemsTagListPaths = Cesty uzl\u016f v stromu seznamu tag\u016f, kter\u00e9 jsou p\u0159ipnuty. \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 82a90e7f6..25a81a02c 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -969,3 +969,9 @@ message.info.importShapes = During importing shapes, you need to select a FOLDER The best way to get the structure right is to export shapes in current SWF file first. import.shape.result = %count% shapes imported. + +pin = Click to pin this item +unpin = Pinned - click to unpin this item. +contextmenu.unpin = Unpin +contextmenu.unpin.all = Unpin all +contextmenu.unpin.others = Unpin others \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index 7805ec12e..018272265 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -940,4 +940,10 @@ message.info.importShapes = B\u011bhem importu tvar\u016f mus\u00edte vybrat SLO Slo\u017eka mus\u00ed obsahovat podslo\u017eku "shapes" a n\u00e1zvy soubor\u016f v n\u00ed mus\u00ed souhlasit s existuj\u00edc\u00edmi tvary v pr\u00e1v\u011b vybran\u00e9m SWF.\r\n \ Nejlep\u0161\u00ed zp\u016fsob jak m\u00edt tuto strukturu spr\u00e1vn\u011b je nejprve exportovat tvary v aktu\u00e1ln\u00edm SWF souboru. -import.shape.result = %count% tvar\u016f importov\u00e1no. \ No newline at end of file +import.shape.result = %count% tvar\u016f importov\u00e1no. + +pin = Kliknut\u00edm tuto polo\u017eku p\u0159ipnete +unpin = P\u0159ipnuto - kliknut\u00edm tuto polo\u017eku odepnete. +contextmenu.unpin = Odepnout +contextmenu.unpin.all = Odepnout v\u0161e +contextmenu.unpin.others = Odepnout ostatn\u00ed \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeRoot.java b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeRoot.java index b5096f348..fa1b82bac 100644 --- a/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeRoot.java +++ b/src/com/jpexs/decompiler/flash/gui/taglistview/TagListTreeRoot.java @@ -17,13 +17,14 @@ package com.jpexs.decompiler.flash.gui.taglistview; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.gui.tagtree.TreeRoot; import com.jpexs.decompiler.flash.treeitems.TreeItem; /** * * @author JPEXS */ -public class TagListTreeRoot implements TreeItem { +public class TagListTreeRoot implements TreeItem, TreeRoot{ @Override public SWF getSwf() { diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java index 5f598c126..b3136ddd4 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java @@ -149,6 +149,28 @@ public abstract class AbstractTagTree extends JTree { return ICONS.get(t); } + public static Icon getIconFor(TreeItem val) { + return getIconFor(val, false); + } + public static Icon getIconFor(TreeItem val, boolean folderExpanded) { + TreeNodeType type = getTreeNodeType(val); + + if (type == TreeNodeType.FOLDER && folderExpanded) { + type = TreeNodeType.FOLDER_OPEN; + } + + if ((type == TreeNodeType.FOLDER || type == TreeNodeType.FOLDER_OPEN) && val instanceof FolderItem) { + FolderItem si = (FolderItem) val; + if (!TagTreeRoot.FOLDER_ROOT.equals(si.getName())) { + String itemName = "folder" + si.getName(); + return View.getIcon(itemName.toLowerCase(Locale.ENGLISH) + "16"); + } + } else { + return getIconForType(type); + } + return null; + } + public AbstractTagTree(AbstractTagTreeModel treeModel, MainPanel mainPanel) { super(treeModel); this.mainPanel = mainPanel; @@ -401,15 +423,27 @@ public abstract class AbstractTagTree extends JTree { } } + public TreePath getTreePathFromString(String pathStr) { + if (pathStr == null || pathStr.length() == 0) { + return null; + } + String[] path = pathStr.split("\\|"); + return View.getTreePathByPathStrings(this, Arrays.asList(path)); + } + + public TreeItem getTreeItemFromPathString(String pathStr) { + TreePath path = getTreePathFromString(pathStr); + if (path == null) { + return null; + } + return (TreeItem) path.getLastPathComponent(); + } + public void setSelectionPathString(String pathStr) { - if (pathStr != null && pathStr.length() > 0) { - String[] path = pathStr.split("\\|"); - - TreePath tp = View.getTreePathByPathStrings(this, Arrays.asList(path)); - if (tp != null) { - // the current view is the Resources view, otherwise tp is null - mainPanel.setTagTreeSelectedNode(this, (TreeItem) tp.getLastPathComponent()); - } + TreeItem item = getTreeItemFromPathString(pathStr); + if (item != null) { + // the current view is the Resources view, otherwise tp is null + mainPanel.setTagTreeSelectedNode(this, item); } } @@ -563,9 +597,16 @@ public abstract class AbstractTagTree extends JTree { return item; } - public String getSelectionPathString() { + public String getItemPathString(TreeItem item) { + TreePath path = getModel().getTreePath(item); + if (path == null) { + return null; + } + return pathToString(path); + } + + public String pathToString(TreePath path) { StringBuilder sb = new StringBuilder(); - TreePath path = getSelectionPath(); if (path != null) { boolean first = true; for (Object p : path.getPath()) { @@ -577,10 +618,13 @@ public abstract class AbstractTagTree extends JTree { sb.append(p.toString()); } } - return sb.toString(); } + public String getSelectionPathString() { + return pathToString(getSelectionPath()); + } + public static TreeNodeType getTagNodeTypeFromTagClass(Class cl) { if ((cl == DefineFontTag.class) || (cl == DefineFont2Tag.class) diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java index 8055f5ea4..79914885e 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java @@ -168,21 +168,7 @@ public class TagTree extends AbstractTagTree { return this; } - TreeNodeType type = getTreeNodeType(val); - - if (type == TreeNodeType.FOLDER && expanded) { - type = TreeNodeType.FOLDER_OPEN; - } - - if ((type == TreeNodeType.FOLDER || type == TreeNodeType.FOLDER_OPEN) && val instanceof FolderItem) { - FolderItem si = (FolderItem) val; - if (!TagTreeRoot.FOLDER_ROOT.equals(si.getName())) { - String itemName = "folder" + si.getName(); - setIcon(View.getIcon(itemName.toLowerCase(Locale.ENGLISH) + "16")); - } - } else { - setIcon(getIconForType(type)); - } + setIcon(getIconFor(val, expanded)); /* boolean isModified = val instanceof Tag && ((Tag) val).isModified(); if(val instanceof ScriptPack){ diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeRoot.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeRoot.java index 89ac72b57..9d749b531 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeRoot.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeRoot.java @@ -22,7 +22,7 @@ import com.jpexs.decompiler.flash.treeitems.FolderItem; * * @author JPEXS */ -public class TagTreeRoot extends FolderItem { +public class TagTreeRoot extends FolderItem implements TreeRoot { public static final String FOLDER_ROOT = "root"; diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TreeRoot.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TreeRoot.java new file mode 100644 index 000000000..e99c28a91 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TreeRoot.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 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.tagtree; + +/** + * + * @author JPEXS + */ +public interface TreeRoot { + +}