From 87b962ea12f6cd63baad4022ab8df904be1c8e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Mon, 5 May 2025 12:30:15 +0200 Subject: [PATCH] Added: #2370 Magnet icon toggle for snap options. Better popups for deobfuscate options + link options. --- CHANGELOG.md | 2 +- .../decompiler/flash/gui/ImagePanel.java | 5 + .../decompiler/flash/gui/PopupButton.java | 109 ++++++++++++++++++ .../decompiler/flash/gui/SoundTagPlayer.java | 5 + .../decompiler/flash/gui/abc/ABCPanel.java | 50 ++++---- .../decompiler/flash/gui/abc/LinkDialog.java | 19 ++- .../flash/gui/abc/SnapOptionsButton.java | 91 +++++++++++++++ .../flash/gui/action/ActionPanel.java | 55 ++++----- .../decompiler/flash/gui/graphics/snap16.png | Bin 0 -> 660 bytes .../decompiler/flash/gui/graphics/snap32.png | Bin 0 -> 1874 bytes .../flash/gui/locales/MainFrame.properties | 8 ++ .../flash/gui/player/FlashPlayerPanel.java | 5 + .../flash/gui/player/MediaDisplay.java | 2 + .../flash/gui/player/ZoomPanel.java | 6 + 14 files changed, 305 insertions(+), 52 deletions(-) create mode 100644 src/com/jpexs/decompiler/flash/gui/PopupButton.java create mode 100644 src/com/jpexs/decompiler/flash/gui/abc/SnapOptionsButton.java create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/snap16.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/snap32.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bfcc4db7..f188ad995 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ All notable changes to this project will be documented in this file. - [#2370] Objects display - Option to show horizontal and vertical rulers - [#2370] Objects display - Create guides by dragging from a ruler - Objects dragging - show touch point and snap it to 9 important points around object rectangle -- [#2370] Snap to guides, objects and pixels, Snap align +- [#2370] Snap to guides, objects and pixels, Snap align, toggle with magnet icon ### Fixed - [#2424] DefineEditText handling of letterSpacing, font size on incorrect values diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index ee9b26781..46722241e 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -399,6 +399,11 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private int guidesCharacterId = -1; + @Override + public boolean canUseSnapping() { + return selectionMode || doFreeTransform || hilightedPoints != null; + } + public void setFrozenButtons(boolean frozenButtons) { this.frozenButtons = frozenButtons; } diff --git a/src/com/jpexs/decompiler/flash/gui/PopupButton.java b/src/com/jpexs/decompiler/flash/gui/PopupButton.java new file mode 100644 index 000000000..37d3a8467 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/PopupButton.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2025 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 java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.Icon; +import javax.swing.JPopupMenu; +import javax.swing.JToggleButton; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; + +/** + * Button with a popup on click. + * The logic ensures that it stays down when menu is shown. + * @author JPEXS + */ +public abstract class PopupButton extends JToggleButton { + + private boolean insideButton = false; + + private PopupMenuListener popupListener; + + private JPopupMenu popupMenu; + + public PopupButton(Icon icon) { + super(icon); + initListeners(); + } + + public PopupButton(String text) { + super(text); + initListeners(); + } + + public PopupButton(String text, Icon icon) { + super(text, icon); + initListeners(); + } + + private void initListeners() { + addMouseListener(new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { + insideButton = true; + } + + @Override + public void mouseExited(MouseEvent e) { + insideButton = false; + } + }); + addActionListener(this::popupOpenActionPerformed); + popupListener = new PopupMenuListener() { + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + if (!insideButton) { + PopupButton.this.setSelected(false); + } + if (popupMenu != null) { + popupMenu.removePopupMenuListener(popupListener); + } + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + + } + }; + } + + /** + * Method for getting (constructing?) a popup menu + * @return A popup menu + */ + protected abstract JPopupMenu getPopupMenu(); + + private void popupOpenActionPerformed(ActionEvent evt) { + if (!isSelected()) { + return; + } + + popupMenu = getPopupMenu(); + popupMenu.addPopupMenuListener(popupListener); + + JToggleButton sourceButton = (JToggleButton) evt.getSource(); + popupMenu.show(sourceButton, 0, sourceButton.getHeight()); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java b/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java index 7144c3e7c..2ecf22982 100644 --- a/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java +++ b/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java @@ -92,6 +92,11 @@ public class SoundTagPlayer implements MediaDisplay { private int instanceId = totalInstances++; + @Override + public boolean canUseSnapping() { + return false; + } + public int getInstanceId() { return instanceId; } diff --git a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java index 627c7f67e..47de868f3 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java @@ -57,6 +57,7 @@ import com.jpexs.decompiler.flash.gui.FasterScrollPane; import com.jpexs.decompiler.flash.gui.HeaderLabel; import com.jpexs.decompiler.flash.gui.Main; import com.jpexs.decompiler.flash.gui.MainPanel; +import com.jpexs.decompiler.flash.gui.PopupButton; import com.jpexs.decompiler.flash.gui.SearchListener; import com.jpexs.decompiler.flash.gui.SearchPanel; import com.jpexs.decompiler.flash.gui.TagEditorPanel; @@ -978,8 +979,23 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener(); libraryComboBox.addItem("AIR (airglobal.swc)"); @@ -1029,9 +1047,8 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener saveListeners = new ArrayList<>(); public void addSaveListener(ActionListener listener) { @@ -74,8 +77,19 @@ public class LinkDialog extends JDialog { } } - public LinkDialog(MainPanel mainPanel) { + public LinkDialog(MainPanel mainPanel, JToggleButton linkButton) { this.mainPanel = mainPanel; + linkButton.addMouseListener(new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { + overLinkButton = true; + } + + @Override + public void mouseExited(MouseEvent e) { + overLinkButton = false; + } + }); setUndecorated(true); setResizable(false); getRootPane().setWindowDecorationStyle(JRootPane.NONE); @@ -86,6 +100,9 @@ public class LinkDialog extends JDialog { public void windowDeactivated(WindowEvent e) { save(swf, false); swf = null; + if (!overLinkButton) { + linkButton.setSelected(false); + } dispose(); } }); diff --git a/src/com/jpexs/decompiler/flash/gui/abc/SnapOptionsButton.java b/src/com/jpexs/decompiler/flash/gui/abc/SnapOptionsButton.java new file mode 100644 index 000000000..640304546 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/abc/SnapOptionsButton.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2025 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.abc; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.gui.AppStrings; +import com.jpexs.decompiler.flash.gui.PopupButton; +import com.jpexs.decompiler.flash.gui.View; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.JCheckBox; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JPopupMenu; + +/** + * + * @author JPEXS + */ +public class SnapOptionsButton extends PopupButton { + + + public SnapOptionsButton() { + super(View.getIcon("snap16")); + setToolTipText(AppStrings.translate("button.snap_options")); + setMargin(new Insets(0, 0, 0, 0)); + } + + @Override + protected JPopupMenu getPopupMenu() { + JPopupMenu popupMenu = new JPopupMenu(); + + JCheckBox snapAlignMenuItem = new JCheckBox(AppStrings.translate("snap_options.snap_align")); + snapAlignMenuItem.setSelected(Configuration.snapAlign.get()); + snapAlignMenuItem.addActionListener(this::snapAlignMenuItemActionPerformed); + popupMenu.add(snapAlignMenuItem); + + JCheckBox snapToGuidesMenuItem = new JCheckBox(AppStrings.translate("snap_options.snap_to_guides")); + snapToGuidesMenuItem.setSelected(Configuration.snapToGuides.get()); + snapToGuidesMenuItem.addActionListener(this::snapToGuidesMenuItemActionPerformed); + popupMenu.add(snapToGuidesMenuItem); + + JCheckBox snapToPixelsMenuItem = new JCheckBox(AppStrings.translate("snap_options.snap_to_pixels")); + snapToPixelsMenuItem.setSelected(Configuration.snapToPixels.get()); + snapToPixelsMenuItem.addActionListener(this::snapToPixelsMenuItemActionPerformed); + popupMenu.add(snapToPixelsMenuItem); + + JCheckBox snapToObjectsMenuItem = new JCheckBox(AppStrings.translate("snap_options.snap_to_objects")); + snapToObjectsMenuItem.setSelected(Configuration.snapToObjects.get()); + snapToObjectsMenuItem.addActionListener(this::snapToObjectsMenuItemActionPerformed); + popupMenu.add(snapToObjectsMenuItem); + + return popupMenu; + } + + private void snapAlignMenuItemActionPerformed(ActionEvent evt) { + JCheckBox menuItem = (JCheckBox) evt.getSource(); + Configuration.snapAlign.set(menuItem.isSelected()); + } + + private void snapToGuidesMenuItemActionPerformed(ActionEvent evt) { + JCheckBox menuItem = (JCheckBox) evt.getSource(); + Configuration.snapToGuides.set(menuItem.isSelected()); + } + + private void snapToPixelsMenuItemActionPerformed(ActionEvent evt) { + JCheckBox menuItem = (JCheckBox) evt.getSource(); + Configuration.snapToPixels.set(menuItem.isSelected()); + } + + private void snapToObjectsMenuItemActionPerformed(ActionEvent evt) { + JCheckBox menuItem = (JCheckBox) evt.getSource(); + Configuration.snapToObjects.set(menuItem.isSelected()); + } + +} diff --git a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java index 2c316888e..97775de56 100644 --- a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java @@ -47,6 +47,7 @@ import com.jpexs.decompiler.flash.gui.GraphDialog; import com.jpexs.decompiler.flash.gui.HeaderLabel; import com.jpexs.decompiler.flash.gui.Main; import com.jpexs.decompiler.flash.gui.MainPanel; +import com.jpexs.decompiler.flash.gui.PopupButton; import com.jpexs.decompiler.flash.gui.ScrollablePanel; import com.jpexs.decompiler.flash.gui.SearchListener; import com.jpexs.decompiler.flash.gui.SearchPanel; @@ -92,6 +93,7 @@ import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JLabel; import javax.swing.JOptionPane; @@ -196,8 +198,8 @@ public class ActionPanel extends JPanel implements SearchListener { decompiledEditor.setShowMarkers(false); if (src.getSwf().needsCalculatingAS2UninitializeClassTraits(src)) { @@ -946,8 +948,23 @@ public class ActionPanel extends JPanel implements SearchListenerE zwlQ_vO+*O2;J;ANRTm3s^TOMr#0uJ&bGa_OvC9$E#iBMZ)M663UhE=R(N%>(8Rj%A z6jr#U)nm`IoqoT)$g{Pg4jg#Sk8{4~oX>d#B7z_YsAcznkPZmwG;a#9$l^1Hff*hP zwFDjjQVzIo@9T40JRXQ9Q{kQGt;gedH#P=2mkTkGU`>Gp5Z&YTy3IX3_!167U0dUw zVK5re#08t1G1uRZAF6s3TtI7VuIRGQM_*$%X}+O>-spoMI_UJWmGRD^i{F`{X3};st?AqrQpm?)|Q|-Bx6EI#9?D zX=ntm6?&6=L~5svemd>6 z(!PtL#j6yXRR0HM<6x)nPSCUGPbm4~DJ_IA)9TLF%Hr}BYFRbdh4waN)1MO_4cNa< uPz`qQ;<<{>|03{5u>T|Q8`x_5CBOh^vK)`ZXgnkU0000}n5&=}At z%F7o7kS3y2Q-e<0WGvO);9@4z)|oW_Bpt_=nW&A)Oq{8m#A(`?YM}MiPCKz}n$oE0 zSf`VWVRk`qQCL{rB#3|nk-$W7*@b=G-P7}37E2r@yQFvK%<|p)-Saz--}!wP9lqWr z9|c~Offooc8u*4GEe(FZA37iq1J3w>7k2=?{D|)6KsQTZ=?Jicfq%1N>sBm#<{4x> z{4hh%q)8G+TrLdS?eNspVBGJ&>;~2y1aAF50#F$IAM>)aZ5vLVf)b5l5n~zIh!kCURegufde?dZyzQAYc=ridsQQ$k{!I2T1+q?JU=Kv-RB8dbb9|G!1EEe0k+FJBH{y2i&-H;4=ej+ij z4l*Nu9#JvOd=QtB*Liw93>;NV<0XM25lD(2%UfGfS6+^$TenKz0z7g8J+CX?WU;OP zeJ!p%@dQE)E*p)|DGCEiP?+(Z;Sjo1b?9G2uQn2$P>H;%s+OAgdAh7<71HrOa1f!k zHr)U0kKy~MKKBg!{z}5xtyZGGKDt&|NLRR*T`4Klm6k@`Y<=nJbcXxQT5yy1m$MbJ zEn$186nOeLC;zG0tnC%=XzxNt!$*$NA6X}OU%E6?=0z5(ZT;_R&|mRAsNGi~rfsIUpb4ODDIw5Jwr z^C9MiKHe&s2`e*qBF)XW1x>jVeD^PR*3D1Nv^{Fsf}sr?5be7L*_5KOos)pV&N{q4 zxEcJ#NpO+%C|h)zA29%-Y@Qw|D+@Zm4>~a|9E9LQ^v2--By~FYORrSdt^Vd}TgA?o z;p%cD($@=xNhBgy*bMx)d(4lHNO&gj+x`lG*UR?rhqP!B2QS*dRp9FB25;P+|ET~J z2LI>kRcmZjPd<(IOHO#Rv(W$EU$|u4bDm|RG?{sOIFtyup4-@6Y_;ZOWo9DQ->-p7 z@izEkni7Yn0#IK0#g5Z!zO{Dqmd!tc+tY?{AP6HLWwc}|uD|;>jC{_yzPJfjD40;@ ztQ&tXuv)U#9Xf>3?b|fX5CoLDhYJ7p2Lc!X-VyJM08Eic#JH;L+xWEWGQ#07^m+r6 zMuss;kP9Eg>F1wCdo-F5xR{<2tF>y~p~D!hd=kWgOYBc#f5K;klkb|Cz#iZY-X9b~ zWH{H*(k$sCrTO`b^UUTntyH4-MuPztS{iY|`-S7R`sPHzZ6fnATP06VFLWl8MnGA# zfE#Zg8it3crV;pn-|=ewXPd`XwrlrxyYpfreR9P^omW4hKOH|z)w_OT7tb(xfXFk6 zR)Y|&f$QX3vzkWdX42@Qg*2LzO=Gz^6v)q`|CmhlYmR~U7qO*&>5Pv9lyjpNj}x5rcQycWXrf?0w*0! z1@Xeqw>fw2-sVgc+`&h*$ULz25%Mi~fP4aYaUP8`aAesM`pBG0uTO$c`BU!swG(g0 zinz_2VY7TpWd8k)3_d`iN7vH$!UZ%gfR`?&AOlAWmr;G1`A%^8PQdAx8YfvGz7EFk z;ckk#&^X6dUiAa0PgNoI8PB>DBI&L`zena}o8UfxrE^1YLPC6%Jo;&#&6jwbRP_!ZHD z`9#Z?5|tE@;~R;CCli&jXYbC_50$R6JXN(7?#rzR1tzq^X`<*sgmZI{;W&pzK4_Yg zD7%zI0GeVh%E^YOvjbuNx)F*gj#`r%!`P;ccz25h&EpeE^*osh7#bbU=8U=NrHk{I z=9yEcDW=|ti_S(kC&nE6>YI`xTrvT~gu|xBmgbC1b5TJ-A^W*EGIJlauBi+~0g!q^3Op5uH*M9{V0EqN9ZMjt|YXATM M07*qoM6N<$g1+ybNB{r; literal 0 HcmV?d00001 diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 850b4c599..9c0fb9d83 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1021,3 +1021,11 @@ message.wiki = Visit FFDec Wiki at: \r\n%url% #after 22.0.1 filter.swf_spl = SWF files (*.swf, *.spl) + +#after 22.0.2 +button.snap_options = Snap options +snap_options.snap_align = Snap Align +snap_options.snap_to_guides = Snap to Guides +snap_options.snap_to_pixels = Snap to Pixels +snap_options.snap_to_objects = Snap to Objects + diff --git a/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java b/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java index 8638a2566..f3de97ae1 100644 --- a/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java @@ -71,6 +71,11 @@ public final class FlashPlayerPanel extends Panel implements Closeable, MediaDis private Color bgColor; + @Override + public boolean canUseSnapping() { + return false; + } + @Override public boolean loopAvailable() { return false; diff --git a/src/com/jpexs/decompiler/flash/gui/player/MediaDisplay.java b/src/com/jpexs/decompiler/flash/gui/player/MediaDisplay.java index ccfc02f38..f8b60f82c 100644 --- a/src/com/jpexs/decompiler/flash/gui/player/MediaDisplay.java +++ b/src/com/jpexs/decompiler/flash/gui/player/MediaDisplay.java @@ -82,4 +82,6 @@ public interface MediaDisplay extends Closeable { public void setMuted(boolean value); public boolean isMutable(); + + public boolean canUseSnapping(); } diff --git a/src/com/jpexs/decompiler/flash/gui/player/ZoomPanel.java b/src/com/jpexs/decompiler/flash/gui/player/ZoomPanel.java index 90c258531..02cc82855 100644 --- a/src/com/jpexs/decompiler/flash/gui/player/ZoomPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/player/ZoomPanel.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.gui.player; import com.jpexs.decompiler.flash.gui.AppStrings; import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.gui.abc.SnapOptionsButton; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.math.BigDecimal; @@ -35,6 +36,7 @@ public class ZoomPanel extends JPanel implements MediaDisplayListener { private MediaDisplay display; private JButton zoomFitButton; + private SnapOptionsButton snapOptionsButton; private final JLabel percentLabel = new JLabel("100%"); private boolean zoomToFit = false; private double realZoom = 1.0; @@ -57,6 +59,8 @@ public class ZoomPanel extends JPanel implements MediaDisplayListener { JButton zoomNoneButton = new JButton(View.getIcon("zoomnone16")); zoomNoneButton.addActionListener(this::zoomNoneButtonActionPerformed); zoomNoneButton.setToolTipText(AppStrings.translate("button.zoomnone.hint")); + + snapOptionsButton = new SnapOptionsButton(); setLayout(new FlowLayout()); add(percentLabel); @@ -64,6 +68,7 @@ public class ZoomPanel extends JPanel implements MediaDisplayListener { add(zoomOutButton); add(zoomNoneButton); add(zoomFitButton); + add(snapOptionsButton); display.addEventListener(this); } @@ -149,6 +154,7 @@ public class ZoomPanel extends JPanel implements MediaDisplayListener { Zoom zoom = display.getZoom(); zoomFitButton.setVisible(zoom != null); percentLabel.setVisible(zoom != null); + snapOptionsButton.setVisible(display.canUseSnapping()); Zoom currentZoom = new Zoom(); currentZoom.fit = zoomToFit; currentZoom.value = realZoom;