From e776bc768cf9afca1867200e25d64d315cd72a3e Mon Sep 17 00:00:00 2001 From: Calvin Morrison Date: Fri, 15 May 2026 10:10:04 -0400 Subject: Full mixer implementation — tray, popup, prefs, devices tab with port indicators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the complete tmix feature set built since the initial skeleton: balance knob, level meters, KLed mute button, system tray with scroll-wheel volume and recording indicator, tray popup, preferences dialog, right-click context menus, single-instance enforcement, scroll area, window geometry persistence, and Devices tab with PA card profile switcher and live port availability indicators (2-column layout, in-place updates on plug/unplug). Co-Authored-By: Claude Sonnet 4.6 --- src/ui/tmixtray.cpp | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 src/ui/tmixtray.cpp (limited to 'src/ui/tmixtray.cpp') diff --git a/src/ui/tmixtray.cpp b/src/ui/tmixtray.cpp new file mode 100644 index 0000000..c0980f0 --- /dev/null +++ b/src/ui/tmixtray.cpp @@ -0,0 +1,182 @@ +#include "tmixtray.h" +#include "tmixpopup.h" +#include "../model/audiodevice.h" +#include "../model/pulsemodel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TmixTray::TmixTray( TQWidget *parent ) + : KSystemTray(parent), m_model(0), m_device(0), m_recTray(0), + m_popup(0), m_recordingCount(0) +{ + setCaption( i18n("Volume Control") ); + + TQPixmap titleIcon = TDEGlobal::iconLoader()->loadIcon( + "kmix", TDEIcon::Small, 16, TDEIcon::DefaultState, 0, true ); + if ( titleIcon.isNull() ) + titleIcon = TDEGlobal::iconLoader()->loadIcon( + "audio-volume-medium", TDEIcon::Small, 16, TDEIcon::DefaultState, 0, true ); + contextMenu()->changeTitle( contextMenu()->idAt(0), titleIcon, i18n("Volume Control") ); + + updateIcon(); +} + +void TmixTray::setDevice( AudioDevice *dev ) +{ + if ( m_device ) + disconnect( m_device, 0, this, 0 ); + m_device = dev; + if ( m_device ) { + connect( m_device, TQ_SIGNAL(volumeChanged(int)), this, TQ_SLOT(updateIcon()) ); + connect( m_device, TQ_SIGNAL(muteChanged(bool)), this, TQ_SLOT(updateIcon()) ); + } + updateIcon(); +} + +void TmixTray::setModel( PulseModel *model ) +{ + m_model = model; + TQPtrList inputs = model->devices( AudioDevice::Input ); + for ( TQPtrListIterator it(inputs); *it; ++it ) { + connect( *it, TQ_SIGNAL(recordingActiveChanged(bool)), + this, TQ_SLOT(onRecordingActive(bool)) ); + } + connect( model, TQ_SIGNAL(deviceAdded(AudioDevice*)), + this, TQ_SLOT(onDeviceAdded(AudioDevice*)) ); +} + +void TmixTray::mousePressEvent( TQMouseEvent *e ) +{ + if ( e->button() == TQt::LeftButton ) { + if ( !m_model ) { KSystemTray::mousePressEvent(e); return; } + + TDEConfig *cfg = TDEGlobal::config(); + cfg->setGroup("General"); + bool showPopup = cfg->readBoolEntry("ShowPopup", true); + + if ( showPopup ) { + if ( !m_popup ) { + m_popup = new TmixPopup( m_model, this ); + connect( m_popup, TQ_SIGNAL(showMixerRequested()), + parentWidget(), TQ_SLOT(show()) ); + } + if ( m_popup->justHidden() ) { e->accept(); return; } + m_popup->toggleAt( mapToGlobal( TQPoint(0,0) ), size() ); + } else { + TQWidget *win = parentWidget(); + win->isVisible() ? win->hide() : win->show(); + } + e->accept(); + return; + } + KSystemTray::mousePressEvent( e ); +} + +void TmixTray::updateIcon() +{ + int vol = m_device ? m_device->volume() : 0; + bool mute = m_device ? m_device->muted() : true; + + const char *pic; + if ( mute || vol == 0 ) pic = "audio-volume-muted.png"; + else if ( vol < 34 ) pic = "audio-volume-low.png"; + else if ( vol < 67 ) pic = "audio-volume-medium.png"; + else pic = "audio-volume-high.png"; + + TQString path = TDEGlobal::dirs()->findResource( "data", + TQString("tmix/pics/crystal/") + pic ); + if ( !path.isEmpty() ) + setPixmap( TQPixmap(path) ); +} + +void TmixTray::onRecordingActive( bool active ) +{ + m_recordingCount += active ? 1 : -1; + if ( m_recordingCount < 0 ) m_recordingCount = 0; + updateRecordingTray(); +} + +void TmixTray::onDeviceAdded( AudioDevice *dev ) +{ + if ( dev->category() == AudioDevice::Input ) + connect( dev, TQ_SIGNAL(recordingActiveChanged(bool)), + this, TQ_SLOT(onRecordingActive(bool)) ); +} + +void TmixTray::updateRecordingTray() +{ + TDEConfig *cfg = TDEGlobal::config(); + cfg->setGroup("General"); + bool showRec = cfg->readBoolEntry("ShowRecordingTray", true); + + if ( m_recordingCount > 0 && showRec ) { + if ( !m_recTray ) { + m_recTray = new KSystemTray( parentWidget() ); + m_recTray->setCaption( i18n("Microphone Active") ); + + TQString path = TDEGlobal::dirs()->findResource( "data", + "tmix/pics/mix_microphone_recording.png" ); + if ( path.isEmpty() ) + path = TDEGlobal::dirs()->findResource( "data", + "tmix/pics/mix_microphone.png" ); + + if ( !path.isEmpty() ) + m_recTray->setPixmap( TQPixmap( path ) ); + } + + // Build tooltip: list names of active recording streams + if ( m_model ) { + TQStringList names; + TQPtrList streams = m_model->devices( AudioDevice::Recording ); + for ( TQPtrListIterator it(streams); *it; ++it ) + names.append( (*it)->name() ); + TQString tip = names.isEmpty() + ? i18n("Microphone in use") + : i18n("Recording: ") + names.join(", "); + TQToolTip::add( m_recTray, tip ); + } + + m_recTray->show(); + } else if ( m_recTray ) { + m_recTray->hide(); + } +} + +void TmixTray::wheelEvent( TQWheelEvent *e ) +{ + if ( !m_device ) return; + TDEConfig *cfg = TDEGlobal::config(); + cfg->setGroup("General"); + int step = cfg->readNumEntry("ScrollStep", 5); + int delta = e->delta() > 0 ? step : -step; + int newVol = m_device->volume() + delta; + if ( newVol < 0 ) newVol = 0; + if ( newVol > 100 ) newVol = 100; + m_device->setVolume( newVol ); +} + +void TmixTray::contextMenuAboutToShow( TDEPopupMenu *menu ) +{ + while ( menu->count() > 1 ) + menu->removeItemAt( 1 ); + + TQWidget *win = parentWidget(); + menu->insertItem( SmallIcon("configure"), i18n("&Configure TMix..."), + win, TQ_SLOT(showPreferences()) ); + menu->insertItem( SmallIcon("help"), i18n("&About TMix"), + win, TQ_SLOT(showAbout()) ); + menu->insertSeparator(); + menu->insertItem( SmallIcon("application-exit"), i18n("&Quit"), + win, TQ_SLOT(quit()) ); +} + +#include "tmixtray.moc" -- cgit v1.2.3