2
* KMix -- KDE's full featured mini mixer
5
* Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de>
6
* Copyright (C) 2001 Preston Brown <pbrown@kde.org>
7
* Copyright (C) 2004 Christian Esken <esken@kde.org>
9
* This program is free software; you can redistribute it and/or
10
* modify it under the terms of the GNU Library General Public
11
* License as published by the Free Software Foundation; either
12
* version 2 of the License, or (at your option) any later version.
14
* This program is distributed in the hope that it will be useful,
15
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17
* Library General Public License for more details.
19
* You should have received a copy of the GNU Library General Public
20
* License along with this program; if not, write to the Free
21
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26
#include <kapplication.h>
29
#include <kglobalsettings.h>
31
#include <kiconloader.h>
33
#include <kwindowsystem.h>
34
#include <kactioncollection.h>
35
#include <ktoggleaction.h>
36
#include <qapplication.h>
38
#include <QDesktopWidget>
39
#include <QMouseEvent>
45
#include <Phonon/MediaObject>
47
#include "gui/dialogselectmaster.h"
48
#include "apps/kmix.h"
49
#include "gui/kmixdockwidget.h"
50
#include "core/mixer.h"
51
#include "gui/mixdevicewidget.h"
52
#include "core/mixertoolbox.h"
53
#include "gui/viewdockareapopup.h"
55
KMixDockWidget::KMixDockWidget(KMixWindow* parent, bool volumePopup)
56
: KStatusNotifierItem(parent)
57
// : KStatusNotifierItem(0)
59
, _playBeepOnVolumeChange(false) // disabled due to triggering a "Bug"
60
, _oldToolTipValue(-1)
62
, _volumePopup(volumePopup)
63
, _kmixMainWindow(parent)
64
, _contextMenuWasOpen(false)
66
setToolTipIconByName("kmix");
67
setCategory(Hardware);
69
m_mixer = Mixer::getGlobalMasterMixer(); // ugly, but we'll live with that for now
70
createMasterVolWidget();
72
connect(this, SIGNAL(scrollRequested(int,Qt::Orientation)), this, SLOT(trayWheelEvent(int)));
73
connect(this, SIGNAL(secondaryActivateRequested(QPoint)), this, SLOT(dockMute()));
74
connect(this, SIGNAL(activateRequested(bool, QPoint)), this, SLOT(activateMenuOrWindow(bool, QPoint)));
75
connect(contextMenu(), SIGNAL(aboutToShow()), this, SLOT(contextMenuAboutToShow()));
78
// TODO minimizeRestore usage is currently VERY broken
79
#warning minimizeRestore usage is currently VERY broken in KMIx. This must be fixed before doing a release.
83
kDebug() << "Construct the ViewDockAreaPopup and actions";
84
_referenceWidget = new KMenu(parent);
85
ViewDockAreaPopup* _referenceWidget2 = new ViewDockAreaPopup(_referenceWidget, "dockArea", Mixer::getGlobalMasterMixer(), 0, (GUIProfile*)0, parent);
86
_referenceWidget2->createDeviceWidgets();
88
_volWA = new QWidgetAction(_referenceWidget);
89
_volWA->setDefaultWidget(_referenceWidget2);
90
_referenceWidget->addAction(_volWA);
92
//setAssociatedWidget(_referenceWidget);
93
//setAssociatedWidget(_referenceWidget); // If you use the popup, associate that instead of the MainWindow
102
KMixDockWidget::~KMixDockWidget()
105
// Note: deleting _volWA also deletes its associated ViewDockAreaPopup (_referenceWidget) and prevents the
106
// action to be left with a dangling pointer.
107
// cesken: I adapted the patch from https://bugs.kde.org/show_bug.cgi?id=220621#c27 to branch /branches/work/kmix
112
void KMixDockWidget::createActions()
114
QMenu *menu = contextMenu();
116
MixDevice* md = Mixer::getGlobalMasterMD();
117
if ( md != 0 && md->playbackVolume().hasSwitch() ) {
118
// Put "Mute" selector in context menu
119
KToggleAction *action = actionCollection()->add<KToggleAction>( "dock_mute" );
120
action->setText( i18n( "M&ute" ) );
121
connect(action, SIGNAL(triggered(bool) ), SLOT( dockMute() ));
122
menu->addAction( action );
125
// Put "Select Master Channel" dialog in context menu
126
if ( m_mixer != 0 ) {
127
QAction *action = actionCollection()->addAction( "select_master" );
128
action->setText( i18n("Select Master Channel...") );
129
connect(action, SIGNAL(triggered(bool) ), SLOT(selectMaster()));
130
menu->addAction( action );
133
// Setup volume preview
134
if ( _playBeepOnVolumeChange ) {
135
_audioPlayer = Phonon::createPlayer(Phonon::MusicCategory);
136
_audioPlayer->setParent(this);
142
KMixDockWidget::createMasterVolWidget()
144
// Reset flags, so that the dock icon will be reconstructed
145
_oldToolTipValue = -1;
146
_oldPixmapType = '-';
148
if (Mixer::getGlobalMasterMD() == 0) {
149
// In case that there is no mixer installed, there will be no controlChanged() signal's
150
// Thus we prepare the dock areas manually
157
m_mixer->readSetFromHWforceUpdate(); // after changing the master device, make sure to re-read (otherwise no "changed()" signals might get sent by the Mixer
158
/* With the recently introduced QSocketNotifier stuff, we can't rely on regular timer updates
159
any longer. Also the readSetFromHWforceUpdate() won't be enough. As a workaround, we trigger
160
all "repaints" manually here.
161
The call to m_mixer->readSetFromHWforceUpdate() is most likely superfluous, even if we don't use QSocketNotifier (e.g. in backends OSS, Solaris, ...)
165
/* We are setting up 3 connections:
166
* Refreshig the _dockAreaPopup (not anymore necessary, because ViewBase already does it)
167
* Refreshing the Tooltip
168
* Refreshing the Icon
170
// connect( m_mixer, SIGNAL(controlChanged()), _dockAreaPopup, SLOT(refreshVolumeLevels()) );
171
connect( m_mixer, SIGNAL(controlChanged()), this, SLOT(setVolumeTip() ) );
172
connect( m_mixer, SIGNAL(controlChanged()), this, SLOT(updatePixmap() ) );
175
void KMixDockWidget::selectMaster()
177
DialogSelectMaster* dsm = new DialogSelectMaster(m_mixer);
178
dsm->setAttribute(Qt::WA_DeleteOnClose, true);
179
connect ( dsm, SIGNAL(newMasterSelected(QString&, QString&)), SLOT( handleNewMaster(QString&, QString&)) );
180
connect ( dsm, SIGNAL(newMasterSelected(QString&, QString&)), SIGNAL( newMasterSelected()) );
185
void KMixDockWidget::handleNewMaster(QString& /*mixerID*/, QString& /*control_id*/)
187
/* When a new master is selected, we will need to destroy *this instance of KMixDockWidget.
188
Reason: This widget is a KSystemTrayIcon, and needs an associated QWidget. This is in
189
our case the ViewDockAreaPopup instance. As that is recreated, we need to recrate ourself as well.
190
The only chance to do so is outside, in fact it is done by KMixWindow::updateDocking(). This needs to
191
use deleteLater(), as we are executing in a SLOT currently.
193
_kmixMainWindow->updateDocking();
195
Mixer *mixer = MixerToolBox::instance()->find(mixerID);
197
kError(67100) << "KMixDockWidget::createPage(): Invalid Mixer (mixerID=" << mixerID << ")" << endl;
198
return; // can not happen
201
//Mixer::setGlobalMaster(mixer->id(), control_id); // We must save this information "somewhere".
202
createMasterVolWidget();
208
KMixDockWidget::setVolumeTip()
210
MixDevice *md = Mixer::getGlobalMasterMD();
212
int newToolTipValue = 0;
216
tip = i18n("Mixer cannot be found"); // !! text could be reworked
217
newToolTipValue = -2;
221
// Playback volume will be used for the DockIcon if available.
222
// This heuristic is "good enough" for the DockIcon for now.
224
Volume& vol = md->playbackVolume();
225
if (! vol.hasVolume() ) {
226
vol = md->captureVolume();
228
if ( vol.hasVolume() && vol.maxVolume() != 0 ) {
229
val = (vol.getAvgVolume(Volume::MMAIN)*100 )/( vol.maxVolume() );
232
// create a new "virtual" value. With that we see "volume changes" as well as "muted changes"
233
newToolTipValue = val;
234
if ( md->isMuted() ) newToolTipValue += 10000;
235
if ( _oldToolTipValue != newToolTipValue ) {
236
tip = i18n( "Volume at %1%", val );
237
if ( md->playbackVolume().hasSwitch() && md->isMuted() ) {
238
tip += i18n( " (Muted)" );
243
// The actual updating is only done when the "toolTipValue" was changed
244
if ( newToolTipValue != _oldToolTipValue ) {
245
// changed (or completely new tooltip)
246
setToolTipTitle(tip);
248
_oldToolTipValue = newToolTipValue;
252
KMixDockWidget::updatePixmap()
254
MixDevice *md = Mixer::getGlobalMasterMD();
261
else if ( md->playbackVolume().hasSwitch() && md->isMuted() )
267
Volume& vol = md->playbackVolume();
268
if (! vol.hasVolume() ) {
269
vol = md->captureVolume();
271
long absoluteVolume = vol.getAvgVolume(Volume::MALL);
272
int percentage = vol.percentage(absoluteVolume);
273
if ( percentage < 25 ) newPixmapType = '1'; // Hint: also negative-values
274
else if ( percentage < 75 ) newPixmapType = '2';
275
else newPixmapType = '3';
279
if ( newPixmapType != _oldPixmapType ) {
280
// Pixmap must be changed => do so
281
switch ( newPixmapType ) {
282
case 'e': setIconByName( "kmixdocked_error" ); break;
283
case 'm': setIconByName( "audio-volume-muted" ); break;
284
case '1': setIconByName( "audio-volume-low" ); break;
285
case '2': setIconByName( "audio-volume-medium" ); break;
286
case '3': setIconByName( "audio-volume-high" ); break;
290
_oldPixmapType = newPixmapType;
295
void KMixDockWidget::activateMenuOrWindow(bool val, const QPoint &pos)
297
kDebug() << "activateMenuOrWindow: " << val << "," << pos;
301
void KMixDockWidget::activate(const QPoint &pos)
303
kDebug() << "Activate at " << pos;
305
bool showHideMainWindow = false;
306
showHideMainWindow |= (_referenceWidget == 0);
307
showHideMainWindow |= (pos.x() == 0 && pos.y() == 0); // HACK. When the action comes from the context menu, the pos is (0,0)
309
if ( showHideMainWindow ) {
310
// Use default KStatusNotifierItem behavior if we are not using the dockAreaPopup
311
kDebug() << "Use default KStatusNotifierItem behavior";
312
setAssociatedWidget(_kmixMainWindow);
313
KStatusNotifierItem::activate(pos);
317
KMenu* dockAreaPopup =_referenceWidget; // TODO Refactor to use _referenceWidget directly
318
kDebug() << "Skip default KStatusNotifierItkdebem behavior";
319
if ( dockAreaPopup->isVisible() ) {
320
dockAreaPopup->hide();
321
kDebug() << "dap is visible => hide and return";
325
// if (dockAreaPopup->isVisible()) {
326
// contextMenu()->hide();
327
// setAssociatedWidget(_kmixMainWindow);
328
// KStatusNotifierItem::activate(pos);
329
// kDebug() << "cm is visible => setAssociatedWidget(_kmixMainWindow)";
334
setAssociatedWidget(_referenceWidget);
335
kDebug() << "cm is NOT visible => setAssociatedWidget(_referenceWidget)";
337
dockAreaPopup->adjustSize();
338
int h = dockAreaPopup->height();
339
int x = pos.x() - dockAreaPopup->width()/2;
342
// kDebug() << "h="<<h<< " x="<<x << " y="<<y<< "gx="<< geometry().x() << "gy="<< geometry().y();
348
dockAreaPopup->move(x, y); // so that the mouse is outside of the widget
349
kDebug() << "moving to" << dockAreaPopup->size() << x << y;
351
dockAreaPopup->show();
353
// Now handle Multihead displays. And also make sure that the dialog is not
354
// moved out-of-the screen on the right (see Bug 101742).
355
const QDesktopWidget* vdesktop = QApplication::desktop();
356
const QRect& vScreenSize = vdesktop->screenGeometry(dockAreaPopup);
357
//const QRect screenGeometry(const QWidget *widget) const
358
if ( (x+dockAreaPopup->width()) > (vScreenSize.width() + vScreenSize.x()) ) {
359
// move horizontally, so that it is completely visible
360
dockAreaPopup->move(vScreenSize.width() + vScreenSize.x() - dockAreaPopup->width() -1 , y);
361
kDebug() << "Multihead: (case 1) moving to" << vScreenSize.x() << "," << vScreenSize.y();
363
else if ( x < vScreenSize.x() ) {
364
// horizontally out-of bound
365
dockAreaPopup->move(vScreenSize.x(), y);
366
kDebug() << "Multihead: (case 2) moving to" << vScreenSize.x() << "," << vScreenSize.y();
368
// the above stuff could also be implemented vertically
370
KWindowSystem::setState( dockAreaPopup->winId(), NET::StaysOnTop | NET::SkipTaskbar | NET::SkipPager );
375
// KMixDockWidget::trayToolTipEvent(QHelpEvent *e ) {
376
// kDebug(67100) << "trayToolTipEvent" ;
381
KMixDockWidget::trayWheelEvent(int delta)
383
MixDevice *md = Mixer::getGlobalMasterMD();
386
Volume vol = md->playbackVolume();
387
if ( md->playbackVolume().hasVolume() )
388
vol = md->playbackVolume();
390
vol = md->captureVolume();
392
int inc = vol.maxVolume() / 20;
394
if ( inc < 1 ) inc = 1;
396
for ( int i = 0; i < vol.count(); i++ ) {
397
int newVal = vol[i] + (inc * (delta / 120));
398
if( newVal < 0 ) newVal = 0;
399
vol.setVolume( (Volume::ChannelID)i, newVal < vol.maxVolume() ? newVal : vol.maxVolume() );
402
if ( _playBeepOnVolumeChange ) {
403
QString fileName("KDE_Beep_Digital_1.ogg");
404
_audioPlayer->setCurrentSource(fileName);
405
_audioPlayer->play();
407
if ( md->playbackVolume().hasVolume() )
408
md->playbackVolume().setVolume(vol);
410
md->captureVolume().setVolume(vol);
411
m_mixer->commitVolumeChange(md);
418
KMixDockWidget::dockMute()
420
MixDevice *md = Mixer::getGlobalMasterMD();
422
md->setMuted( !md->isMuted() );
423
md->mixer()->commitVolumeChange( md );
428
KMixDockWidget::contextMenuAboutToShow()
430
// KStatusNotifierItem::contextMenuAboutToShow();
432
kDebug() << "<<< mm 1";
433
QAction* showAction = actionCollection()->action("minimizeRestore");
434
kDebug() << "<<< mm 2";
435
if ( parent() && showAction )
437
if ( ((QWidget*)parent())->isVisible() ) // TODO isVisible() is not good enough here
439
showAction->setText( i18n("Hide Mixer Window") );
443
showAction->setText( i18n("Show Mixer Window") );
445
kDebug() << "<<< mm 3";
446
//disconnect(showAction, 0, 0, 0);
447
//connect(showAction, SIGNAL(triggered(bool), this, hideOrShowMainWindow() );
451
// Enable/Disable "Muted" menu item
452
MixDevice* md = Mixer::getGlobalMasterMD();
453
KToggleAction *dockMuteAction = static_cast<KToggleAction*>(actionCollection()->action("dock_mute"));
454
kDebug(67100) << "---> md=" << md << "dockMuteAction=" << dockMuteAction << "isMuted=" << md->isMuted();
455
if ( md != 0 && dockMuteAction != 0 ) {
456
bool hasSwitch = md->playbackVolume().hasSwitch();
457
dockMuteAction->setEnabled( hasSwitch );
458
dockMuteAction->setChecked( hasSwitch && md->isMuted() );
460
_contextMenuWasOpen = true;
463
#include "kmixdockwidget.moc"