Commit ec34615b authored by MarkG's avatar MarkG Committed by TIGERs GitLab
Browse files

Resolve "Make shortcuts for stepping through simulation and replay working globally"

Closes #1202

See merge request main/Sumatra!1455

sumatra-commit: a222d8fc060f7c47aea321f10761252f12745738
parent 3c8b84a5
Pipeline #17118 passed with stage
in 4 minutes and 8 seconds
/*
* Copyright (c) 2009 - 2020, DHBW Mannheim - TIGERs Mannheim
* Copyright (c) 2009 - 2021, DHBW Mannheim - TIGERs Mannheim
*/
package edu.tigers.sumatra;
import edu.tigers.sumatra.model.SumatraModel;
import edu.tigers.sumatra.util.GlobalShortcuts;
import edu.tigers.sumatra.util.ScalingUtil;
import lombok.extern.log4j.Log4j2;
import net.infonode.gui.laf.InfoNodeLookAndFeel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
......@@ -24,10 +24,9 @@ import java.util.Properties;
/**
* Base for MainPresenter
*/
@Log4j2
public abstract class AMainPresenter implements IMainFrameObserver
{
private static final Logger log = LogManager.getLogger(AMainPresenter.class.getName());
private static final String LAYOUT_CONFIG_PATH = "./config/gui/";
private final AMainFrame mainFrame;
......@@ -144,6 +143,8 @@ public abstract class AMainPresenter implements IMainFrameObserver
@Override
public void onExit()
{
GlobalShortcuts.removeAllForFrame(getMainFrame());
// ### Persist user settings
final Properties appProps = SumatraModel.getInstance().getUserSettings();
......
/*
* Copyright (c) 2009 - 2020, DHBW Mannheim - TIGERs Mannheim
* Copyright (c) 2009 - 2021, DHBW Mannheim - TIGERs Mannheim
*/
package edu.tigers.sumatra.util;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.swing.JFrame;
import javax.swing.KeyStroke;
import java.awt.Component;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
/**
......@@ -23,143 +24,79 @@ import java.util.concurrent.ConcurrentHashMap;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class GlobalShortcuts
{
private static final Map<EShortcut, ShortcutWrapper> SHORTCUTS = new ConcurrentHashMap<>();
private static final List<UiShortcut> UI_SHORTCUTS = new CopyOnWriteArrayList<>();
private static class ShortcutWrapper
{
private KeyEventDispatcher dispatcher;
}
/**
* Enum which contains the available Key shortcuts
*/
public enum EKeyModifier
public static void add(
String name,
Component component,
Runnable runnable,
KeyStroke keyStroke)
{
CTRL,
ALT,
SHIFT,
;
/**
* Method to evaluate if the correct keys were pressed.
*
* @param e
* @return
*/
public boolean isKeyPressed(KeyEvent e)
{
switch (this)
KeyEventDispatcher dispatcher = e -> {
var eventKeyStroke = KeyStroke.getKeyStrokeForEvent(e);
if (eventKeyStroke.equals(keyStroke))
{
case CTRL:
return e.isControlDown();
case ALT:
return e.isAltDown();
case SHIFT:
return e.isShiftDown();
default:
return false;
var rootComponent = findRootComponent(component);
Component eventComponent = e.getComponent();
if (eventComponent != null && findRootComponent(eventComponent) == rootComponent)
{
runnable.run();
return true;
}
}
}
}
/**
* Enum with all available global shortcuts
*/
@Getter
public enum EShortcut
{
EMERGENCY_MODE(KeyEvent.VK_ESCAPE, "esc"),
MATCH_MODE(KeyEvent.VK_F1, "F1"),
REFEREE_START(KeyEvent.VK_F2, "F2"),
REFEREE_STOP(KeyEvent.VK_F3, "F3"),
REFEREE_HALT(KeyEvent.VK_F4, "F4"),
CHARGE_ALL_BOTS(KeyEvent.VK_F5, "F5"),
DISCHARGE_ALL_BOTS(KeyEvent.VK_F6, "F6"),
RESET_FIELD(KeyEvent.VK_F7, "F7"),
REFBOX_HALT(KeyEvent.VK_SEPARATOR, "SEPARATOR", EKeyModifier.CTRL),
REFBOX_STOP(KeyEvent.VK_NUMPAD0, "NUMPAD0", EKeyModifier.CTRL),
REFBOX_START_NORMAL(KeyEvent.VK_ENTER, "ENTER", EKeyModifier.CTRL),
REFBOX_START_FORCE(KeyEvent.VK_NUMPAD5, "NUMPAD5", EKeyModifier.CTRL),
REFBOX_KICKOFF_YELLOW(KeyEvent.VK_NUMPAD1, "NUMPAD1", EKeyModifier.CTRL),
REFBOX_KICKOFF_BLUE(KeyEvent.VK_NUMPAD3, "NUMPAD3", EKeyModifier.CTRL),
REFBOX_INDIRECT_YELLOW(KeyEvent.VK_NUMPAD4, "NUMPAD4", EKeyModifier.CTRL),
REFBOX_INDIRECT_BLUE(KeyEvent.VK_NUMPAD6, "NUMPAD6", EKeyModifier.CTRL),
REFBOX_DIRECT_YELLOW(KeyEvent.VK_NUMPAD7, "NUMPAD7", EKeyModifier.CTRL),
REFBOX_DIRECT_BLUE(KeyEvent.VK_NUMPAD9, "NUMPAD9", EKeyModifier.CTRL),
AUTOREF_TOGGLE(KeyEvent.VK_F12, "F12"),
;
private final int key;
private final String desc;
private final Set<EKeyModifier> modifiers;
return false;
};
EShortcut(final int key, final String desc, final EKeyModifier... modifiers)
{
this.key = key;
this.desc = desc;
this.modifiers = new HashSet<>(Arrays.asList(modifiers));
}
String keys = InputEvent.getModifiersExText(keyStroke.getModifiers())
+ "+"
+ KeyEvent.getKeyText(keyStroke.getKeyCode());
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(dispatcher);
UI_SHORTCUTS.add(UiShortcut.builder()
.name(name)
.keys(keys)
.component(component)
.dispatcher(dispatcher)
.build()
);
}
/**
* @param shortcut
* @param run
*/
public static void register(final EShortcut shortcut, final Runnable run)
public static void removeAllForFrame(JFrame frame)
{
KeyEventDispatcher ked = e -> {
if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == shortcut.key)
{
final boolean optionalKeyPressed = isOptionalKeyPressedForShortcut(e, shortcut);
if (optionalKeyPressed)
{
run.run();
}
}
return false;
};
ShortcutWrapper w = new ShortcutWrapper();
w.dispatcher = ked;
SHORTCUTS.put(shortcut, w);
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(ked);
UI_SHORTCUTS.stream().filter(s -> findRootComponent(s.getComponent()) == frame).forEach(s -> {
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(s.getDispatcher());
UI_SHORTCUTS.remove(s);
});
}
private static boolean isOptionalKeyPressedForShortcut(KeyEvent e, EShortcut shortcut)
public static void removeAllForComponent(Component component)
{
boolean keyIsPressed = true;
UI_SHORTCUTS.stream().filter(s -> s.getComponent() == component).forEach(s -> {
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(s.getDispatcher());
UI_SHORTCUTS.remove(s);
});
}
for (EKeyModifier modifier : shortcut.getModifiers())
{
keyIsPressed &= modifier.isKeyPressed(e);
}
return keyIsPressed;
public static List<UiShortcut> getShortcuts(Component component)
{
var rootComponent = findRootComponent(component);
return UI_SHORTCUTS.stream()
.filter(s -> findRootComponent(s.getComponent()) == rootComponent)
.collect(Collectors.toUnmodifiableList());
}
/**
* @param shortcut
*/
public static void unregisterAll(final EShortcut shortcut)
private static Component findRootComponent(Component component)
{
ShortcutWrapper shortcutWrapper = SHORTCUTS.remove(shortcut);
if (shortcutWrapper != null)
Component parent = component.getParent();
if (parent == null)
{
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(shortcutWrapper.dispatcher);
return component;
}
return findRootComponent(parent);
}
}
/*
* *********************************************************
* Copyright (c) 2009 DHBW Mannheim - Tigers Mannheim
* Project: tigers-centralSoftware
* Date: 29.08.2009
* Authors: Bernhard Perun <bernhard.perun@googlemail.com>
* *********************************************************
* Copyright (c) 2009 - 2021, DHBW Mannheim - TIGERs Mannheim
*/
package edu.tigers.sumatra.util;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.util.Arrays;
import java.util.Comparator;
import java.util.stream.Collectors;
import edu.tigers.sumatra.components.BetterScrollPane;
import net.miginfocom.swing.MigLayout;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import edu.tigers.sumatra.components.BetterScrollPane;
import edu.tigers.sumatra.util.GlobalShortcuts.EShortcut;
import net.miginfocom.swing.MigLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Toolkit;
/**
* Show available shortcuts
*/
public class ShortcutsDialog extends JDialog
{
// --------------------------------------------------------------------------
// --- instance-variables ---------------------------------------------------
// --------------------------------------------------------------------------
private static final long serialVersionUID = 3461893941869192656L;
// --------------------------------------------------------------------------
// --- constructor(s) -------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Initializes about - dialog.
*/
public ShortcutsDialog()
public ShortcutsDialog(Container parent)
{
// --- window configuration ---
this.setSize(320, 400);
this.setSize(400, 400);
setResizable(false);
setTitle("Shortcuts");
setModal(true);
// --- alignment: center on screen ---
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension screenDimension = tk.getScreenSize();
this.setLocation((int) (screenDimension.getWidth() - getWidth()) / 2,
(int) (screenDimension.getHeight() - getHeight()) / 2);
JPanel scPanel = new JPanel(new MigLayout("fill", ""));
for (EShortcut eShortcut : Arrays.stream(EShortcut.values()).sorted(Comparator.comparing(Enum::name))
.collect(Collectors.toList()))
for (UiShortcut shortcut : GlobalShortcuts.getShortcuts(parent))
{
JLabel lblId = new JLabel(eShortcut.name());
JLabel lblDesc = new JLabel(eShortcut.getDesc());
JLabel lblId = new JLabel(shortcut.getName());
JLabel lblDesc = new JLabel(shortcut.getKeys());
scPanel.add(lblId);
scPanel.add(lblDesc, "wrap");
}
BetterScrollPane scrollPane = new BetterScrollPane(scPanel);
add(scrollPane);
setVisible(true);
}
}
/*
* Copyright (c) 2009 - 2021, DHBW Mannheim - TIGERs Mannheim
*/
package edu.tigers.sumatra.util;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import java.awt.Component;
import java.awt.KeyEventDispatcher;
@Value
@Builder
public class UiShortcut
{
@NonNull
String name;
@NonNull
String keys;
@NonNull
Component component;
@NonNull
KeyEventDispatcher dispatcher;
}
......@@ -4,10 +4,9 @@
package edu.tigers.sumatra.view.toolbar;
import edu.tigers.sumatra.model.SumatraModel;
import edu.tigers.sumatra.util.GlobalShortcuts;
import edu.tigers.sumatra.util.GlobalShortcuts.EShortcut;
import edu.tigers.sumatra.util.ImageScaler;
import edu.tigers.sumatra.view.FpsPanel;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import net.miginfocom.swing.MigLayout;
......@@ -18,11 +17,9 @@ import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
......@@ -34,41 +31,38 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class ToolBar
{
private final List<IToolbarObserver> observers = new CopyOnWriteArrayList<>();
private final JButton btnRecSave = new JButton();
private final JToolBar jToolBar;
private final JButton btnRecSave;
@Getter
private final JToolBar jToolBar = new JToolBar();
@Getter
private final FpsPanel fpsPanel = new FpsPanel();
@Getter
private final JProgressBar heapBar = new JProgressBar();
@Getter
private final JLabel heapLabel = new JLabel();
/**
* The toolbar
*/
public ToolBar()
{
log.trace("Create toolbar");
var btnEmergency = new JButton();
btnEmergency.setForeground(Color.red);
btnEmergency.addActionListener(new EmergencyStopListener());
btnEmergency.addActionListener(actionEvent -> notifyEmergencyStop());
btnEmergency.setIcon(ImageScaler.scaleDefaultButtonImageIcon("/stop-emergency.png"));
btnEmergency.setToolTipText("Emergency stop [Esc]");
btnEmergency.setBorder(BorderFactory.createEmptyBorder());
btnEmergency.setBackground(new Color(0, 0, 0, 1));
btnRecSave = new JButton();
btnRecSave.addActionListener(new RecordSaveButtonListener());
btnRecSave.addActionListener(actionEvent -> toggleRecord());
btnRecSave.setIcon(ImageScaler.scaleDefaultButtonImageIcon("/record.png"));
btnRecSave.setToolTipText("Start/Stop recording");
btnRecSave.setBorder(BorderFactory.createEmptyBorder());
btnRecSave.setBackground(new Color(0, 0, 0, 1));
var btnTournament = new JToggleButton();
btnTournament.addActionListener(new TournamentListener());
btnTournament.addActionListener(this::toggleTournamentMode);
btnTournament.setIcon(ImageScaler.scaleDefaultButtonImageIcon("/tournament_bw.png"));
btnTournament.setToolTipText("Tournament mode (off)");
btnTournament.setBorder(BorderFactory.createEmptyBorder());
......@@ -84,7 +78,6 @@ public class ToolBar
heapBar.setToolTipText("Memory Usage");
// --- configure toolbar ---
jToolBar = new JToolBar();
jToolBar.setFloatable(false);
jToolBar.setRollover(true);
......@@ -104,13 +97,6 @@ public class ToolBar
{
log.trace("Load button icon " + icon.name());
}
GlobalShortcuts.register(EShortcut.EMERGENCY_MODE, () -> {
for (final IToolbarObserver o : observers)
{
o.onEmergencyStop();
}
});
}
......@@ -132,29 +118,6 @@ public class ToolBar
}
// --------------------------------------------------------------------------
// --- getter/setter --------------------------------------------------------
// --------------------------------------------------------------------------
/**
* @return
*/
public JToolBar getToolbar()
{
return jToolBar;
}
/**
* @return the fpsPanel
*/
public FpsPanel getFpsPanel()
{
return fpsPanel;
}
/**
* @param recording
*/
......@@ -172,73 +135,35 @@ public class ToolBar
}
private class EmergencyStopListener implements ActionListener
{
@Override
public void actionPerformed(final ActionEvent e)
{
for (final IToolbarObserver o : observers)
{
o.onEmergencyStop();
}
SwingUtilities.invokeLater(jToolBar::repaint);
}
}
private class TournamentListener implements ActionListener
private void notifyEmergencyStop()
{
@Override
public void actionPerformed(final ActionEvent e)
{
JToggleButton btn = (JToggleButton) e.getSource();
SumatraModel.getInstance().setProductive(btn.isSelected());
if (btn.isSelected())
{
btn.setIcon(ImageScaler.scaleDefaultButtonImageIcon("/tournament_color.png"));
btn.setToolTipText("Tournament mode (on)");
} else
{
btn.setIcon(ImageScaler.scaleDefaultButtonImageIcon("/tournament_bw.png"));
btn.setToolTipText("Tournament mode (off)");
}
SwingUtilities.invokeLater(jToolBar::repaint);
}
observers.forEach(IToolbarObserver::onEmergencyStop);
}
private class RecordSaveButtonListener implements ActionListener
private void toggleTournamentMode(ActionEvent e)
{
@Override
public void actionPerformed(final ActionEvent e)
JToggleButton btn = (JToggleButton) e.getSource();
SumatraModel.getInstance().setProductive(btn.isSelected());
if (btn.isSelected())
{
btn.setIcon(ImageScaler.scaleDefaultButtonImageIcon("/tournament_color.png"));
btn.setToolTipText("Tournament mode (on)");
} else
{
Thread t = new Thread(() -> {
btnRecSave.setEnabled(false);
for (IToolbarObserver observer : observers)
{
observer.onToggleRecord();
}
btnRecSave.setEnabled(true);
}, "RecordSaveButton");
t.start();
btn.setIcon(ImageScaler.scaleDefaultButtonImageIcon("/tournament_bw.png"));
btn.setToolTipText("Tournament mode (off)");
}
jToolBar.repaint();
}
/**
* @return the heapBar
*/
public final JProgressBar getHeapBar()
{
return heapBar;
}
/**
* @return the heapLabel
*/
public final JLabel getHeapLabel()