mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 15:17:24 -05:00
The main window's font-glyph icons were rendered with IconFontSwing.buildIcon, which bakes a fixed colour into the image (black by default, DARK_GRAY for the Playlist Editor tab), so the Screen Capture camera, the Playlist Editor tab icon and the View menu icons ignored the selected theme and were nearly invisible on dark themes. Adds ThemedFontIcon, which resolves its colour from UIManager at paint time (Label.foreground, MenuItem.selectionForeground while a menu item is armed, Label.disabledForeground when disabled) and re-renders only when the colour changes, so icons follow theme switches without a restart. Claude-Session: https://claude.ai/code/session_01TDsxnQntujmycg3aJyRsuN Co-authored-by: Dennis Sheirer <dennis.sheirer@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
306 lines
10 KiB
Java
306 lines
10 KiB
Java
/*
|
|
* *****************************************************************************
|
|
* Copyright (C) 2014-2026 Dennis Sheirer
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>
|
|
* ****************************************************************************
|
|
*/
|
|
package io.github.dsheirer.audio.playback;
|
|
|
|
import io.github.dsheirer.alias.AliasModel;
|
|
import io.github.dsheirer.audio.AudioEvent;
|
|
import io.github.dsheirer.eventbus.MyEventBus;
|
|
import io.github.dsheirer.gui.preference.PreferenceEditorType;
|
|
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
|
import io.github.dsheirer.icon.IconModel;
|
|
import io.github.dsheirer.preference.UserPreferences;
|
|
import io.github.dsheirer.sample.Listener;
|
|
import io.github.dsheirer.settings.SettingsManager;
|
|
import java.awt.Color;
|
|
import java.awt.EventQueue;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.event.MouseListener;
|
|
import jiconfont.icons.font_awesome.FontAwesome;
|
|
import io.github.dsheirer.gui.theme.ThemedFontIcon;
|
|
import net.miginfocom.swing.MigLayout;
|
|
|
|
import javax.sound.sampled.FloatControl;
|
|
import javax.swing.Icon;
|
|
import javax.swing.ImageIcon;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JMenuItem;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JPopupMenu;
|
|
import javax.swing.JSlider;
|
|
import javax.swing.SwingUtilities;
|
|
|
|
/**
|
|
* Audio playback panel
|
|
*/
|
|
public class AudioPanel extends JPanel implements Listener<AudioEvent>
|
|
{
|
|
private static final ImageIcon MUTED_ICON = IconModel.getScaledIcon("images/audio_muted.png", 20);
|
|
private static final ImageIcon UNMUTED_ICON = IconModel.getScaledIcon("images/audio_unmuted.png", 20);
|
|
private final AliasModel mAliasModel;
|
|
private final AudioPlaybackManager mAudioPlaybackManager;
|
|
private final IconModel mIconModel;
|
|
private final SettingsManager mSettingsManager;
|
|
private final UserPreferences mUserPreferences;
|
|
private AudioChannelsPanel mAudioChannelsPanel;
|
|
private JButton mMuteButton;
|
|
|
|
/**
|
|
* Constructs an instance
|
|
* @param iconModel for icon lookup
|
|
* @param userPreferences for preference lookup
|
|
* @param settingsManager to monitor for changes
|
|
* @param audioPlaybackManager for accessing the audio output
|
|
* @param aliasModel for alias lookup
|
|
*/
|
|
public AudioPanel(IconModel iconModel, UserPreferences userPreferences, SettingsManager settingsManager,
|
|
AudioPlaybackManager audioPlaybackManager, AliasModel aliasModel)
|
|
{
|
|
mIconModel = iconModel;
|
|
mSettingsManager = settingsManager;
|
|
mAudioPlaybackManager = audioPlaybackManager;
|
|
mAliasModel = aliasModel;
|
|
mUserPreferences = userPreferences;
|
|
mAudioPlaybackManager.addAudioEventListener(this);
|
|
init();
|
|
}
|
|
|
|
/**
|
|
* Initialize the display
|
|
*/
|
|
private void init()
|
|
{
|
|
setLayout(new MigLayout("insets 0 0 0 0", "[]0[grow,fill]", "[fill]0[]"));
|
|
setBackground(Color.BLACK);
|
|
mMuteButton = new MuteButton();
|
|
mMuteButton.setBackground(getBackground());
|
|
add(mMuteButton);
|
|
mAudioChannelsPanel = new AudioChannelsPanel(mIconModel, mUserPreferences, mSettingsManager, mAudioPlaybackManager, mAliasModel);
|
|
add(mAudioChannelsPanel);
|
|
addMouseListener(new MouseSelectionListener());
|
|
}
|
|
|
|
/**
|
|
* Receive audio event notifications from the audio playback controller
|
|
*/
|
|
@Override
|
|
public void receive(AudioEvent event)
|
|
{
|
|
switch(event.getType())
|
|
{
|
|
case AUDIO_CONFIGURATION_CHANGE_STARTED:
|
|
break;
|
|
case AUDIO_CONFIGURATION_CHANGE_COMPLETE:
|
|
EventQueue.invokeLater(() -> {
|
|
remove(mAudioChannelsPanel);
|
|
mAudioChannelsPanel.dispose();
|
|
mAudioChannelsPanel = new AudioChannelsPanel(mIconModel, mUserPreferences, mSettingsManager, mAudioPlaybackManager, mAliasModel);
|
|
add(mAudioChannelsPanel);
|
|
mAudioChannelsPanel.repaint();
|
|
revalidate();
|
|
repaint();
|
|
});
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Audio output mute control menu item.
|
|
*/
|
|
public static class AudioOutputMuteItem extends JMenuItem
|
|
{
|
|
private final AudioOutput mAudioOutput;
|
|
|
|
/**
|
|
* Constructs an instance
|
|
* @param audioOutput to mute/unmute
|
|
*/
|
|
public AudioOutputMuteItem(AudioOutput audioOutput)
|
|
{
|
|
super(audioOutput.isMuted() ? "Unmute" : "Mute");
|
|
mAudioOutput = audioOutput;
|
|
addActionListener(e -> mAudioOutput.setMuted(!mAudioOutput.isMuted()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mouse listener
|
|
*/
|
|
public class MouseSelectionListener implements MouseListener
|
|
{
|
|
@Override
|
|
public void mouseClicked(MouseEvent event)
|
|
{
|
|
if(SwingUtilities.isRightMouseButton(event))
|
|
{
|
|
JPopupMenu popup = new JPopupMenu();
|
|
JMenuItem outputMenu = new JMenuItem("Audio Playback Device ...");
|
|
Icon icon = new ThemedFontIcon(FontAwesome.COG, 14);
|
|
outputMenu.setIcon(icon);
|
|
outputMenu.addActionListener(e -> MyEventBus.getGlobalEventBus()
|
|
.post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.AUDIO_OUTPUT)));
|
|
popup.add(outputMenu);
|
|
|
|
//Add optional gain controller for th eaudio output
|
|
if(mAudioPlaybackManager.getAudioOutput() != null && mAudioPlaybackManager.getAudioOutput().hasGainControl())
|
|
{
|
|
popup.add(new JPopupMenu.Separator());
|
|
JMenuItem volume = new JMenuItem("Audio Volume");
|
|
volume.setEnabled(false);
|
|
Icon volumeIcon = new ThemedFontIcon(FontAwesome.VOLUME_UP, 14);
|
|
volume.setIcon(volumeIcon);
|
|
popup.add(volume);
|
|
popup.add(new VolumeSlider(mAudioPlaybackManager.getAudioOutput().getGainControl()));
|
|
}
|
|
|
|
popup.show(event.getComponent(), event.getX(), event.getY());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void mousePressed(MouseEvent e)
|
|
{
|
|
}
|
|
|
|
@Override
|
|
public void mouseReleased(MouseEvent e)
|
|
{
|
|
}
|
|
|
|
@Override
|
|
public void mouseEntered(MouseEvent e)
|
|
{
|
|
}
|
|
|
|
@Override
|
|
public void mouseExited(MouseEvent e)
|
|
{
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Audio volume (gain) adjustment slider control
|
|
*/
|
|
public static class VolumeSlider extends JSlider
|
|
{
|
|
private final FloatControl mFloatControl;
|
|
|
|
/**
|
|
* Constructs an instance
|
|
* @param control to be controlled
|
|
*/
|
|
public VolumeSlider(FloatControl control)
|
|
{
|
|
super(0, 100, 0);
|
|
|
|
setMajorTickSpacing(25);
|
|
setMinorTickSpacing(5);
|
|
setPaintTicks(true);
|
|
setPaintLabels(true);
|
|
mFloatControl = control;
|
|
setValue(getIntegerValue(mFloatControl.getValue()));
|
|
addChangeListener(event -> mFloatControl.shift(mFloatControl.getValue(),
|
|
getFloatValue(VolumeSlider.this.getValue()),
|
|
1000));
|
|
|
|
addMouseListener(new MouseListener()
|
|
{
|
|
@Override
|
|
public void mouseClicked(MouseEvent event)
|
|
{
|
|
if(event.getClickCount() == 2)
|
|
{
|
|
VolumeSlider.this.setValue(50);
|
|
}
|
|
}
|
|
public void mouseReleased(MouseEvent arg0) {}
|
|
public void mousePressed(MouseEvent arg0) {}
|
|
public void mouseExited(MouseEvent arg0) {}
|
|
public void mouseEntered(MouseEvent arg0) {}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Converts the integer value to a floating point value to use in the
|
|
* float control. Assumes an integer value of 50 is the 0.0 dB mid
|
|
* point (ie no gain ) value.
|
|
*/
|
|
private int getIntegerValue(float value)
|
|
{
|
|
if(value == 0.0f)
|
|
{
|
|
return 50;
|
|
}
|
|
else if(value < 0.0f)
|
|
{
|
|
float ratio = value / mFloatControl.getMinimum();
|
|
|
|
return 50 - (int) (ratio * 50.0f);
|
|
}
|
|
else
|
|
{
|
|
float ratio = value / mFloatControl.getMaximum();
|
|
|
|
return 50 + (int) (ratio * 50.0f);
|
|
}
|
|
}
|
|
|
|
private float getFloatValue(int value)
|
|
{
|
|
if(value == 50)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
else if(value < 50)
|
|
{
|
|
return (float) (50 - value) / 50.0f * mFloatControl.getMinimum();
|
|
}
|
|
else
|
|
{
|
|
return (float) (value - 50) / 50.0f * mFloatControl.getMaximum();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mute button to mute all audio output channels exposed by the audio
|
|
* controller
|
|
*/
|
|
public class MuteButton extends JButton
|
|
{
|
|
private boolean mMuted = false;
|
|
|
|
public MuteButton()
|
|
{
|
|
setIcon(UNMUTED_ICON);
|
|
setBorderPainted(false);
|
|
getAccessibleContext().setAccessibleName("Mute");
|
|
addActionListener(e -> {
|
|
mMuted = !mMuted;
|
|
mAudioPlaybackManager.getAudioOutput().setMuted(mMuted);
|
|
EventQueue.invokeLater(() -> {
|
|
setIcon(mMuted ? MUTED_ICON : UNMUTED_ICON);
|
|
getAccessibleContext().setAccessibleName(mMuted ? "Unmute" : "Mute");
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|