You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
587 lines
16 KiB
587 lines
16 KiB
/*
|
|
* KMix -- KDE's full featured mini mixer
|
|
*
|
|
*
|
|
* Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de>
|
|
* Copyright (C) 2001 Preston Brown <pbrown@kde.org>
|
|
* Copyright (C) 2003 Sven Leiber <s.leiber@web.de>
|
|
* Copyright (C) 2004 Christian Esken <esken@kde.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this program; if not, write to the Free
|
|
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <kpanelapplet.h>
|
|
#include <kdialog.h>
|
|
#include <kaudioplayer.h>
|
|
#include <kiconloader.h>
|
|
#include <kdebug.h>
|
|
#include <khelpmenu.h>
|
|
#include <twin.h>
|
|
#include <tdeaction.h>
|
|
#include <tdeapplication.h>
|
|
#include <tdelocale.h>
|
|
#include <tdepopupmenu.h>
|
|
#include <tdeglobalsettings.h>
|
|
#include <kstandarddirs.h>
|
|
#include <tdemainwindow.h>
|
|
|
|
#include <tqapplication.h>
|
|
#include <tqcursor.h>
|
|
#include <tqtooltip.h>
|
|
#include <tqimage.h>
|
|
#include <X11/Xlib.h>
|
|
#include <fixx11h.h>
|
|
|
|
#include "dialogselectmaster.h"
|
|
#include "mixer.h"
|
|
#include "mixdevicewidget.h"
|
|
#include "kmixdockwidget.h"
|
|
#include "kmixsettings.h"
|
|
#include "viewdockareapopup.h"
|
|
|
|
KMixDockWidget::KMixDockWidget( Mixer *mixer, TQWidget *parent, const char *name, bool volumePopup, bool dockIconMuting )
|
|
: KSystemTray( parent, name ),
|
|
m_mixer(mixer),
|
|
_dockAreaPopup(0L),
|
|
_audioPlayer(0L),
|
|
_playBeepOnVolumeChange(false), // disabled due to triggering a "Bug"
|
|
_oldToolTipValue(-1),
|
|
_oldPixmapType('-'),
|
|
_volumePopup(volumePopup),
|
|
_dockIconMuting(dockIconMuting),
|
|
_dsm(NULL)
|
|
{
|
|
Mixer* preferredMasterMixer = Mixer::masterCard();
|
|
if ( preferredMasterMixer != 0 ) {
|
|
m_mixer = preferredMasterMixer;
|
|
}
|
|
MixDevice* mdMaster = Mixer::masterCardDevice();
|
|
if ( mdMaster != 0 ) {
|
|
m_mixer->setMasterDevice(mdMaster->getPK()); // !! using both Mixer::masterCard() and m_mixer->masterDevice() is nonsense !!
|
|
}
|
|
createActions();
|
|
createMasterVolWidget();
|
|
connect(this, TQ_SIGNAL(quitSelected()), kapp, TQ_SLOT(quitExtended()));
|
|
|
|
TDEGlobal::dirs()->addResourceDir("icons_crystal", locate("appdata", "pics/crystal/"));
|
|
TDEGlobal::dirs()->addResourceDir("icons_oldcrystal", locate("appdata", "pics/oldcrystal/"));
|
|
}
|
|
|
|
KMixDockWidget::~KMixDockWidget()
|
|
{
|
|
if (_dsm)
|
|
{
|
|
delete _dsm;
|
|
}
|
|
delete _audioPlayer;
|
|
delete _dockAreaPopup;
|
|
}
|
|
|
|
void KMixDockWidget::createActions()
|
|
{
|
|
TDEPopupMenu *popupMenu = contextMenu();
|
|
|
|
// Put "Mute" selector in context menu
|
|
(void)new TDEToggleAction(i18n("M&ute"), 0, this, TQ_SLOT(dockMute()),
|
|
actionCollection(), "dock_mute");
|
|
TDEAction *a = actionCollection()->action("dock_mute");
|
|
if (a)
|
|
{
|
|
a->plug(popupMenu);
|
|
}
|
|
|
|
// Put "Select Master Channel" dialog in context menu
|
|
if (m_mixer)
|
|
{
|
|
(void)new TDEAction(i18n("Select Master Channel..."), 0, this, TQ_SLOT(selectMaster()),
|
|
actionCollection(), "select_master");
|
|
a = actionCollection()->action("select_master");
|
|
if (a)
|
|
{
|
|
a->plug(popupMenu);
|
|
}
|
|
}
|
|
// Show/hide mixer window (use "minimizeRestore" action
|
|
a = actionCollection()->action("minimizeRestore");
|
|
if (a)
|
|
{
|
|
a->plug(popupMenu);
|
|
}
|
|
|
|
popupMenu->insertSeparator();
|
|
|
|
// KMix Options
|
|
TDEMainWindow *toplevel = static_cast<TDEMainWindow*>(parent());
|
|
a = toplevel->actionCollection()->action(KStdAction::name(KStdAction::Preferences));
|
|
|
|
if (a)
|
|
{
|
|
a->plug(popupMenu);
|
|
}
|
|
|
|
// Help and quit
|
|
popupMenu->insertItem(SmallIcon("help"), KStdGuiItem::help().text(), (new KHelpMenu(this, TDEGlobal::instance()->aboutData(), false))->menu(), false);
|
|
popupMenu->insertSeparator();
|
|
a = actionCollection()->action(KStdAction::name(KStdAction::Quit));
|
|
if (a)
|
|
{
|
|
a->plug(popupMenu);
|
|
}
|
|
|
|
// Setup volume preview
|
|
if (_playBeepOnVolumeChange)
|
|
{
|
|
_audioPlayer = new KAudioPlayer("KDE_Beep_Digital_1.ogg");
|
|
}
|
|
}
|
|
|
|
void KMixDockWidget::createMasterVolWidget()
|
|
{
|
|
// Reset flags, so that the dock icon will be reconstructed
|
|
_oldToolTipValue = -1;
|
|
_oldPixmapType = '-';
|
|
|
|
if (m_mixer == 0) {
|
|
// In case that there is no mixer installed, there will be no newVolumeLevels() signal's
|
|
// Thus we prepare the dock areas manually
|
|
setVolumeTip();
|
|
updatePixmap(false);
|
|
return;
|
|
}
|
|
// create devices
|
|
if (_dockAreaPopup)
|
|
{
|
|
// Delete the old popup widget if we are changing the channel
|
|
deleteMasterVolWidget();
|
|
}
|
|
|
|
_dockAreaPopup = new ViewDockAreaPopup(0, "dockArea", m_mixer, 0, this);
|
|
_dockAreaPopup->createDeviceWidgets();
|
|
m_mixer->readSetFromHWforceUpdate(); // after changing the master device, make sure to re-read (otherwise no "changed()" signals might get sent by the Mixer
|
|
/* With the recently introduced TQSocketNotifier stuff, we can't rely on regular timer updates
|
|
any longer. Also the readSetFromHWforceUpdate() won't be enough. As a workaround, we trigger
|
|
all "repaints" manually here.
|
|
The call to m_mixer->readSetFromHWforceUpdate() is most likely superfluous, even if we don't use TQSocketNotifier (e.g. in backends OSS, Solaris, ...)
|
|
*/
|
|
setVolumeTip();
|
|
updatePixmap(false);
|
|
/* We are setting up 3 connections:
|
|
* Refreshig the _dockAreaPopup (not anymore neccesary, because ViewBase already does it)
|
|
* Refreshing the Tooltip
|
|
* Refreshing the Icon
|
|
*
|
|
*/
|
|
connect(m_mixer, TQ_SIGNAL(newVolumeLevels()), this, TQ_SLOT(setVolumeTip()));
|
|
connect(m_mixer, TQ_SIGNAL(newVolumeLevels()), this, TQ_SLOT(slotUpdatePixmap()));
|
|
}
|
|
|
|
void KMixDockWidget::deleteMasterVolWidget()
|
|
{
|
|
if (_dockAreaPopup)
|
|
{
|
|
delete _dockAreaPopup;
|
|
_dockAreaPopup = NULL;
|
|
}
|
|
if (m_mixer)
|
|
{
|
|
disconnect(m_mixer, TQ_SIGNAL(newVolumeLevels()), this, TQ_SLOT(setVolumeTip()));
|
|
disconnect(m_mixer, TQ_SIGNAL(newVolumeLevels()), this, TQ_SLOT(slotUpdatePixmap()));
|
|
}
|
|
}
|
|
|
|
void KMixDockWidget::slotUpdatePixmap()
|
|
{
|
|
updatePixmap(false);
|
|
}
|
|
|
|
void KMixDockWidget::selectMaster()
|
|
{
|
|
if (!_dsm)
|
|
{
|
|
_dsm = new DialogSelectMaster(m_mixer);
|
|
connect(_dsm, TQ_SIGNAL(newMasterSelected(bool, int, const TQString&)), TQ_SLOT( handleNewMaster(bool, int, const TQString&)));
|
|
}
|
|
_dsm->show(m_mixer);
|
|
}
|
|
|
|
|
|
void KMixDockWidget::handleNewMaster(bool defaultMaster, int soundcard_id, const TQString &channel_id)
|
|
{
|
|
//kdDebug(67100) << "KMixDockWidget::handleNewMaster() default master=" << defaultMaster << ", soundcard_id=" << soundcard_id << ", channel_id=" << channel_id << endl;
|
|
kapp->config()->setGroup(0);
|
|
kapp->config()->writeEntry("UseDefaultMaster", defaultMaster);
|
|
Mixer *mixer;
|
|
TQString channel = TQString::null;
|
|
if (defaultMaster)
|
|
{
|
|
mixer = Mixer::mixers().first();
|
|
if (mixer)
|
|
{
|
|
MixSet ms = mixer->getMixSet();
|
|
for (MixDevice *md = ms.first(); md != 0; md = ms.next())
|
|
{
|
|
if (!md->isRecordable() && !md->isSwitch() && !md->isEnum())
|
|
{
|
|
channel = md->getPK();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mixer = Mixer::mixers().at(soundcard_id);
|
|
channel = channel_id;
|
|
}
|
|
|
|
if (!mixer || channel.isEmpty()) {
|
|
kdError(67100) << "KMixDockWidget::createPage(): Invalid Mixer (default master=" << defaultMaster << ", soundcard_id="
|
|
<< soundcard_id << ", channel_id=" << channel_id << ")" << endl;
|
|
return; // can not happen
|
|
}
|
|
deleteMasterVolWidget();
|
|
m_mixer = mixer;
|
|
Mixer::setMasterCard(mixer->id()); // We must save this information "somewhere".
|
|
Mixer::setMasterCardDevice(channel);
|
|
createMasterVolWidget();
|
|
}
|
|
|
|
|
|
long
|
|
KMixDockWidget::getAvgVolume()
|
|
{
|
|
MixDevice *md = 0;
|
|
if ( _dockAreaPopup != 0 ) {
|
|
md = _dockAreaPopup->dockDevice();
|
|
}
|
|
|
|
if ( md == 0 || md->maxVolume() == 0 )
|
|
return -1;
|
|
|
|
return (md->getVolume().getAvgVolume(Volume::MMAIN)*100 )/( md->maxVolume() );
|
|
}
|
|
|
|
void
|
|
KMixDockWidget::setVolumeTip()
|
|
{
|
|
MixDevice *md = 0;
|
|
if ( _dockAreaPopup != 0 ) {
|
|
md = _dockAreaPopup->dockDevice();
|
|
}
|
|
|
|
TQString tip = "";
|
|
|
|
int newToolTipValue = 0;
|
|
if ( md == 0 )
|
|
{
|
|
tip = i18n("Mixer cannot be found"); // !! text could be reworked
|
|
newToolTipValue = -2;
|
|
}
|
|
else
|
|
{
|
|
long val = getAvgVolume();
|
|
newToolTipValue = val + 10000*md->isMuted();
|
|
if ( _oldToolTipValue != newToolTipValue ) {
|
|
tip = i18n( "Volume at %1%" ).arg( val );
|
|
if ( md->isMuted() ) {
|
|
tip += i18n( " (Muted)" );
|
|
}
|
|
}
|
|
// create a new "virtual" value. With that we see "volume changes" as well as "muted changes"
|
|
newToolTipValue = val + 10000*md->isMuted();
|
|
}
|
|
|
|
// The actual updating is only done when the "toolTipValue" was changed
|
|
if ( newToolTipValue != _oldToolTipValue ) {
|
|
// changed (or completely new tooltip)
|
|
if ( _oldToolTipValue >= 0 ) {
|
|
// there was an old Tooltip: remove it
|
|
TQToolTip::remove(this);
|
|
}
|
|
TQToolTip::add(this, tip);
|
|
}
|
|
_oldToolTipValue = newToolTipValue;
|
|
}
|
|
|
|
void
|
|
KMixDockWidget::updatePixmap(bool force)
|
|
{
|
|
MixDevice *md = 0;
|
|
if ( _dockAreaPopup != 0 ) {
|
|
md = _dockAreaPopup->dockDevice();
|
|
}
|
|
char newPixmapType;
|
|
if ( md == 0 )
|
|
{
|
|
newPixmapType = 'e';
|
|
}
|
|
else if ( md->isMuted() )
|
|
{
|
|
newPixmapType = 'm';
|
|
}
|
|
else
|
|
{
|
|
long avgVol = getAvgVolume();
|
|
if ( avgVol <= 33 )
|
|
newPixmapType = 'L';
|
|
else if ( avgVol <= 67 )
|
|
newPixmapType = 'M';
|
|
else
|
|
newPixmapType = 'H';
|
|
}
|
|
|
|
if (( newPixmapType != _oldPixmapType ) || (force == true)) {
|
|
// Pixmap must be changed => do so
|
|
// Honor Free Desktop specifications that allow for arbitrary system tray icon sizes
|
|
TQPixmap origpixmap;
|
|
TQPixmap scaledpixmap;
|
|
TQImage newIcon;
|
|
|
|
TQStringList fallback;
|
|
switch ( newPixmapType ) {
|
|
case 'm': fallback << "audio-volume-muted" << "kmixdocked_mute"; break;
|
|
case 'L': fallback << "audio-volume-low" << "kmixdocked"; break;
|
|
case 'M': fallback << "audio-volume-medium" << "kmixdocked"; break;
|
|
case 'H': fallback << "audio-volume-high" << "kmixdocked"; break;
|
|
}
|
|
|
|
TQString icon = getIconPath(fallback);
|
|
if (icon.isNull())
|
|
{
|
|
icon = getIconPath("audio-volume-error");
|
|
}
|
|
|
|
origpixmap = isShown() ? loadSizedIcon(icon, width()) : loadIcon(icon);
|
|
newIcon = origpixmap;
|
|
if (isShown()) {
|
|
newIcon = newIcon.smoothScale(width(), height());
|
|
}
|
|
scaledpixmap = newIcon;
|
|
setPixmap(scaledpixmap);
|
|
|
|
_oldPixmapType = newPixmapType;
|
|
}
|
|
}
|
|
|
|
TQString KMixDockWidget::getIconPath(TQStringList fallback)
|
|
{
|
|
auto iconTheme = KMixSettings::iconTheme();
|
|
|
|
TQCString iconThemeName;
|
|
if (iconTheme != KMixSettings::EnumIconTheme::System)
|
|
{
|
|
switch (iconTheme)
|
|
{
|
|
case KMixSettings::EnumIconTheme::OldCrystal:
|
|
iconThemeName = "oldcrystal";
|
|
break;
|
|
|
|
default:
|
|
case KMixSettings::EnumIconTheme::Crystal:
|
|
iconThemeName = "crystal";
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (TQStringList::iterator it = fallback.begin(); it != fallback.end(); ++it)
|
|
{
|
|
if (iconTheme == KMixSettings::EnumIconTheme::System)
|
|
{
|
|
TQString iconPath = kapp->iconLoader()->iconPath((*it), TDEIcon::Panel, true);
|
|
if (!iconPath.isNull())
|
|
{
|
|
return iconPath;
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
TQCString type = "icons_" + iconThemeName;
|
|
|
|
TQString iconPath = TDEGlobal::dirs()->findResource(type, TQString("%1.png").arg(*it));
|
|
if (!iconPath.isNull()) return iconPath;
|
|
|
|
iconPath = TDEGlobal::dirs()->findResource(type, TQString("%1.svg").arg(*it));
|
|
if (!iconPath.isNull()) return iconPath;
|
|
}
|
|
}
|
|
return TQString::null;
|
|
}
|
|
|
|
void KMixDockWidget::resizeEvent ( TQResizeEvent * )
|
|
{
|
|
updatePixmap(true);
|
|
}
|
|
|
|
void KMixDockWidget::showEvent ( TQShowEvent *se )
|
|
{
|
|
updatePixmap(true);
|
|
}
|
|
|
|
void
|
|
KMixDockWidget::mousePressEvent(TQMouseEvent *me)
|
|
{
|
|
if ( _dockAreaPopup == 0 ) {
|
|
return KSystemTray::mousePressEvent(me);
|
|
}
|
|
|
|
// esken: Due to overwhelming request, LeftButton shows the ViewDockAreaPopup, if configured
|
|
// to do so. Otherwise the main window will be shown.
|
|
if ( me->button() == TQt::LeftButton )
|
|
{
|
|
if ( ! _volumePopup ) {
|
|
// Case 1: User wants to show main window => This is the KSystemTray default action
|
|
return KSystemTray::mousePressEvent(me);
|
|
}
|
|
|
|
// Case 2: User wants to show volume popup
|
|
if ( _dockAreaPopup->justHidden() )
|
|
return;
|
|
|
|
if ( _dockAreaPopup->isVisible() )
|
|
{
|
|
_dockAreaPopup->hide();
|
|
return;
|
|
}
|
|
|
|
int h = _dockAreaPopup->height();
|
|
int x = this->mapToGlobal( TQPoint( 0, 0 ) ).x() + this->width()/2 - _dockAreaPopup->width()/2;
|
|
int y = this->mapToGlobal( TQPoint( 0, 0 ) ).y() - h;
|
|
if ( y < 0 )
|
|
y = y + h + this->height();
|
|
|
|
_dockAreaPopup->move(x, y); // so that the mouse is outside of the widget
|
|
|
|
// Now handle Multihead displays. And also make sure that the dialog is not
|
|
// moved out-of-the screen on the right (see Bug 101742).
|
|
TQDesktopWidget* vdesktop = TQApplication::desktop();
|
|
const TQRect& vScreenSize = vdesktop->screenGeometry(_dockAreaPopup);
|
|
if ( (x+_dockAreaPopup->width()) > (vScreenSize.width() + vScreenSize.x()) ) {
|
|
// move horizontally, so that it is completely visible
|
|
_dockAreaPopup->move(vScreenSize.width() + vScreenSize.x() - _dockAreaPopup->width() -1 , y);
|
|
} // horizontally out-of bound
|
|
else if ( x < vScreenSize.x() ) {
|
|
_dockAreaPopup->move(vScreenSize.x(), y);
|
|
}
|
|
// the above stuff could also be implemented vertically
|
|
|
|
_dockAreaPopup->show();
|
|
KWin::setState(_dockAreaPopup->winId(), NET::StaysOnTop | NET::SkipTaskbar | NET::SkipPager );
|
|
|
|
TQWidget::mousePressEvent(me); // KSystemTray's shouldn't do the default action for this
|
|
return;
|
|
} // LeftMouseButton pressed
|
|
else if ( me->button() == TQt::MidButton ) {
|
|
if ( ! _dockIconMuting ) {
|
|
toggleActive();
|
|
} else {
|
|
dockMute();
|
|
}
|
|
return;
|
|
}
|
|
else {
|
|
KSystemTray::mousePressEvent(me);
|
|
} // Other MouseButton pressed
|
|
|
|
}
|
|
|
|
void
|
|
KMixDockWidget::mouseReleaseEvent( TQMouseEvent *me )
|
|
{
|
|
|
|
KSystemTray::mouseReleaseEvent(me);
|
|
}
|
|
|
|
void
|
|
KMixDockWidget::wheelEvent(TQWheelEvent *e)
|
|
{
|
|
MixDevice *md = 0;
|
|
if ( _dockAreaPopup != 0 ) {
|
|
md = _dockAreaPopup->dockDevice();
|
|
}
|
|
if ( md != 0 )
|
|
{
|
|
Volume vol = md->getVolume();
|
|
int inc = vol.maxVolume() / 20;
|
|
|
|
if ( inc == 0 ) inc = 1;
|
|
|
|
for ( int i = 0; i < vol.count(); i++ ) {
|
|
int newVal = vol[i] + (inc * (e->delta() / 120));
|
|
if( newVal < 0 ) newVal = 0;
|
|
vol.setVolume( (Volume::ChannelID)i, newVal < vol.maxVolume() ? newVal : vol.maxVolume() );
|
|
}
|
|
|
|
if ( _playBeepOnVolumeChange ) {
|
|
_audioPlayer->play();
|
|
}
|
|
md->getVolume().setVolume(vol);
|
|
m_mixer->commitVolumeChange(md);
|
|
// refresh the toolTip (TQt removes it on a MouseWheel event)
|
|
// Mhhh, it doesn't work. TQt does not show it again.
|
|
setVolumeTip();
|
|
// Simulate a mouse move to make TQt show the tooltip again
|
|
TQApplication::postEvent( this, new TQMouseEvent( TQEvent::MouseMove, TQCursor::pos(), TQt::NoButton, TQt::NoButton ) );
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
KMixDockWidget::dockMute()
|
|
{
|
|
MixDevice *md = 0;
|
|
if ( _dockAreaPopup != 0 )
|
|
{
|
|
md = _dockAreaPopup->dockDevice();
|
|
if ( md != 0 ) {
|
|
md->setMuted( !md->isMuted() );
|
|
m_mixer->commitVolumeChange( md );
|
|
updatePixmap(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
KMixDockWidget::contextMenuAboutToShow( TDEPopupMenu* /* menu */ )
|
|
{
|
|
TDEAction* showAction = actionCollection()->action("minimizeRestore");
|
|
if ( parentWidget() && showAction )
|
|
{
|
|
if ( parentWidget()->isVisible() )
|
|
{
|
|
showAction->setText( i18n("Hide Mixer Window") );
|
|
}
|
|
else
|
|
{
|
|
showAction->setText( i18n("Show Mixer Window") );
|
|
}
|
|
}
|
|
|
|
// Enable/Disable "Muted" menu item
|
|
MixDevice *md = 0;
|
|
if ( _dockAreaPopup != 0 )
|
|
{
|
|
md = _dockAreaPopup->dockDevice();
|
|
TDEToggleAction *dockMuteAction = static_cast<TDEToggleAction*>(actionCollection()->action("dock_mute"));
|
|
//kdDebug(67100) << "---> md=" << md << "dockMuteAction=" << dockMuteAction << "isMuted=" << md->isMuted() << endl;
|
|
if ( md != 0 && dockMuteAction != 0 ) {
|
|
dockMuteAction->setChecked( md->isMuted() );
|
|
}
|
|
}
|
|
}
|
|
|
|
#include "kmixdockwidget.moc"
|
|
|