package com.ts3client.ui; import com.ts3client.hotkey.Hotkey; import com.ts3client.hotkey.HotkeyEngine; import javax.swing.SwingUtilities; import java.util.List; /** * Carries a fired hotkey out on the client. * *

Activations arrive on the input hook's thread, so everything is handed to the * event dispatch thread first. Which connections an action reaches is the binding's * "on active server" flag: set, only the selected tab; clear, every connected one. */ final class HotkeyActions implements HotkeyEngine.Handler { /** How much one press of the master-volume hotkeys moves the slider. */ private static final double VOLUME_STEP = 0.05; private final MainFrame frame; /** Latched push-to-talk, driven by the "Toggle Push-to-Talk" action. */ private boolean pttLatched; HotkeyActions(MainFrame frame) { this.frame = frame; } @Override public void onHotkey(Hotkey hotkey, boolean active) { SwingUtilities.invokeLater(() -> perform(hotkey, active)); } private void perform(Hotkey hotkey, boolean active) { List targets = frame.hotkeyTargets(hotkey.activeServerOnly); switch (hotkey.action) { case CONNECT_CURRENT_TAB -> frame.connectBookmark(hotkey.argument, false); case CONNECT_NEW_TAB -> frame.connectBookmark(hotkey.argument, true); case DISCONNECT_CURRENT -> { ServerTab tab = frame.selectedTab(); if (tab != null) tab.disconnect(); } case DISCONNECT_ALL -> { for (ServerTab tab : frame.allTabs()) tab.disconnect(); } case MIC_ACTIVATE -> frame.moveMicrophoneToSelectedTab(); case MIC_MUTE -> setMicMuted(targets, true); case MIC_UNMUTE -> setMicMuted(targets, false); case MIC_TOGGLE -> setMicMuted(targets, !anyMicMuted(targets)); case MIC_LOCAL_MUTE -> setMicLocalMuted(targets, true); case MIC_LOCAL_UNMUTE -> setMicLocalMuted(targets, false); case MIC_LOCAL_TOGGLE -> setMicLocalMuted(targets, !anyMicLocalMuted(targets)); case SPEAKER_MUTE -> setDeafened(targets, true); case SPEAKER_UNMUTE -> setDeafened(targets, false); case SPEAKER_TOGGLE -> setDeafened(targets, !anyDeafened(targets)); case AWAY_SET -> setAway(targets, true, ""); case AWAY_ONLINE -> setAway(targets, false, ""); case AWAY_TOGGLE -> setAway(targets, !anyAway(targets), ""); case AWAY_TOGGLE_WITH_MESSAGE -> setAway(targets, !anyAway(targets), hotkey.argument); case COMMANDER_ACTIVATE -> setCommander(targets, true); case COMMANDER_DEACTIVATE -> setCommander(targets, false); case COMMANDER_TOGGLE -> setCommander(targets, !anyCommander(targets)); // Momentary: the engine reports the release too, so the key simply holds it open. case PTT_ACTIVATE -> frame.setPushToTalk(active || pttLatched); case PTT_DEACTIVATE -> { pttLatched = false; frame.setPushToTalk(false); } case PTT_TOGGLE -> { pttLatched = !pttLatched; frame.setPushToTalk(pttLatched); } case CHANNEL_SWITCH -> { for (ServerTab tab : targets) tab.joinChannelPath(hotkey.argument); } case SERVER_TAB_SELECT -> frame.selectTabNumber(parseIndex(hotkey.argument)); case SERVER_TAB_NEXT -> frame.stepTab(1); case SERVER_TAB_PREVIOUS -> frame.stepTab(-1); case SOUND_MUTE -> frame.setSoundsMuted(true); case SOUND_UNMUTE -> frame.setSoundsMuted(false); case SOUND_TOGGLE -> frame.setSoundsMuted(!frame.areSoundsMuted()); case VOLUME_INCREASE -> frame.adjustMasterVolume(VOLUME_STEP); case VOLUME_DECREASE -> frame.adjustMasterVolume(-VOLUME_STEP); case NICKNAME_CHANGE -> frame.changeNickname(hotkey.argument); case FILEBROWSER -> frame.browseCurrentChannel(); case SKIN_RELOAD -> frame.reloadSkin(); case BRING_TO_FRONT -> frame.bringToFront(); case SEND_TO_BACK -> frame.sendToBack(); default -> { // Listed for completeness in the action catalogue, but not implemented here. } } } private void setMicMuted(List targets, boolean muted) { for (ServerTab tab : targets) tab.setMicMuted(muted); frame.refreshAfterHotkey(); } private void setMicLocalMuted(List targets, boolean muted) { for (ServerTab tab : targets) tab.setMicLocalMuted(muted); frame.refreshAfterHotkey(); } private void setDeafened(List targets, boolean deaf) { for (ServerTab tab : targets) tab.setDeafened(deaf); frame.refreshAfterHotkey(); } private void setAway(List targets, boolean away, String message) { for (ServerTab tab : targets) tab.setAway(away, message == null ? "" : message); frame.refreshAfterHotkey(); } private void setCommander(List targets, boolean commander) { for (ServerTab tab : targets) tab.setCommander(commander); frame.refreshAfterHotkey(); } private static boolean anyMicMuted(List tabs) { return tabs.stream().anyMatch(ServerTab::isMicMuted); } private static boolean anyMicLocalMuted(List tabs) { return tabs.stream().anyMatch(ServerTab::isMicLocalMuted); } private static boolean anyDeafened(List tabs) { return tabs.stream().anyMatch(ServerTab::isDeafened); } private static boolean anyAway(List tabs) { return tabs.stream().anyMatch(ServerTab::isAway); } private static boolean anyCommander(List tabs) { return tabs.stream().anyMatch(ServerTab::isCommander); } /** @return the 1-based tab number in the argument, or 1 when it is not a number */ private static int parseIndex(String argument) { try { return Math.max(1, Integer.parseInt(argument.trim())); } catch (RuntimeException e) { return 1; } } }