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 SearchListener