3 * KMix -- KDE's full featured mini mixer
5 * Copyright 1996-2000 Christian Esken <esken@kde.org>
6 * Copyright 2000-2003 Christian Esken <esken@kde.org>, Stefan Schimanski <1Stein@gmx.de>
7 * Copyright 2002-2007 Christian Esken <esken@kde.org>, Helio Chissini de Castro <helio@conectiva.com.br>
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.
25 // include files for QT
28 #include <qradiobutton.h>
31 // include files for KDE
32 #include <kcombobox.h>
33 #include <kiconloader.h>
34 #include <kmessagebox.h>
39 #include <kapplication.h>
40 #include <kstandardaction.h>
42 #include <khelpmenu.h>
44 #include <kxmlguifactory.h>
46 #include <kactioncollection.h>
47 #include <ktoggleaction.h>
50 #include "mixertoolbox.h"
52 #include "kmixdevicemanager.h"
53 #include "kmixerwidget.h"
54 #include "kmixprefdlg.h"
55 #include "kmixdockwidget.h"
56 #include "kmixtoolbox.h"
57 #include "viewdockareapopup.h"
58 //#include "osd.h" // Postponed to KDE4.1
62 * Constructs a mixer window (KMix main window)
64 KMixWindow::KMixWindow()
68 // m_isVisible (false), // initialize, as we don't trigger a hideEvent()
69 // m_visibilityUpdateAllowed( true ),
70 m_multiDriverMode (false), // -<- I never-ever want the multi-drivermode to be activated by accident
74 setObjectName("KMixWindow");
75 // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in
76 setAttribute(Qt::WA_DeleteOnClose
, false);
78 initActions(); // init actions first, so we can use them in the loadConfig() already
79 loadConfig(); // Load config before initMixer(), e.g. due to "MultiDriver" keyword
82 MixerToolBox::instance()->initMixer(m_multiDriverMode
, m_hwInfoString
);
83 KMixDeviceManager
*theKMixDeviceManager
= KMixDeviceManager::instance();
85 theKMixDeviceManager
->initHotplug();
86 connect(theKMixDeviceManager
, SIGNAL( plugged( const char*, const QString
&, QString
&)), SLOT (plugged( const char*, const QString
&, QString
&) ) );
87 connect(theKMixDeviceManager
, SIGNAL( unplugged( const QString
&)), SLOT (unplugged( const QString
&) ) );
89 show(); // Started visible: We don't do "m_isVisible = true;", as the showEvent() already does it
91 connect( kapp
, SIGNAL( aboutToQuit()), SLOT( saveConfig()) );
95 KMixWindow::~KMixWindow()
98 MixerToolBox::instance()->deinitMixer();
102 void KMixWindow::initActions()
105 KStandardAction::quit( this, SLOT(quit()), actionCollection());
108 _actionShowMenubar
= KStandardAction::showMenubar( this, SLOT(toggleMenuBar()), actionCollection());
109 //actionCollection()->addAction( a->objectName(), a );
110 KStandardAction::preferences( this, SLOT(showSettings()), actionCollection());
111 KStandardAction::keyBindings( guiFactory(), SLOT(configureShortcuts()), actionCollection());
113 QAction
*action
= actionCollection()->addAction( "hwinfo" );
114 action
->setText( i18n( "Hardware &Information" ) );
115 connect(action
, SIGNAL(triggered(bool) ), SLOT( slotHWInfo() ));
116 action
= actionCollection()->addAction( "hide_kmixwindow" );
117 action
->setText( i18n( "Hide Mixer Window" ) );
118 connect(action
, SIGNAL(triggered(bool) ), SLOT(hideOrClose()));
119 action
->setShortcut(QKeySequence(Qt::Key_Escape
));
120 action
= actionCollection()->addAction("toggle_channels_currentview");
121 action
->setText(i18n("Configure &Channels"));
122 connect(action
, SIGNAL(triggered(bool) ), SLOT(slotConfigureCurrentView()));
123 createGUI( "kmixui.rc" );
127 void KMixWindow::initPrefDlg()
129 m_prefDlg
= new KMixPrefDlg( this );
130 connect( m_prefDlg
, SIGNAL(signalApplied(KMixPrefDlg
*)), SLOT(applyPrefs(KMixPrefDlg
*)) );
136 void KMixWindow::initWidgets()
138 // Main widget and layout
139 setCentralWidget( new QWidget( this ) );
142 m_widgetsLayout
= new QVBoxLayout( centralWidget() );
143 m_widgetsLayout
->setObjectName( "m_widgetsLayout" );
144 m_widgetsLayout
->setSpacing( 0 );
145 m_widgetsLayout
->setMargin ( 0 );
148 m_wsMixers
= new KTabWidget( centralWidget() );
149 connect( m_wsMixers
, SIGNAL( currentChanged ( int ) ), SLOT( newMixerShown(int)) );
151 m_widgetsLayout
->addWidget(m_wsMixers
);
153 // show menubar if the actions says so (or if the action does not exist)
154 menuBar()->setVisible( (_actionShowMenubar
==0) || _actionShowMenubar
->isChecked());
156 m_widgetsLayout
->activate();
161 * Updates the docking icon by recreating it.
162 * @returns Whether the docking succeeded. Failure usually means that there
163 * was no suitable mixer control selected.
165 bool KMixWindow::updateDocking()
167 // delete old dock widget
170 // If this is called during a master control change, the dock widget is currently active, so we use deleteLater().
171 m_dockWidget
->deleteLater();
174 if ( _dockAreaPopup
) {
175 // If this is called during a master control change, we rather play safe by using deleteLater().
176 _dockAreaPopup
->deleteLater();
180 if ( m_showDockWidget
== false || Mixer::mixers().count() == 0 ) {
184 // create dock widget and the corresponding popup
185 /* A GUIProfile does not make sense for the DockAreaPopup => Using (GUIProfile*)0 */
186 QWidget
* referenceWidgetForSystray
= this;
187 if ( m_volumeWidget
) {
188 _dockAreaPopup
= new ViewDockAreaPopup(0, "dockArea", Mixer::getGlobalMasterMixer(), 0, (GUIProfile
*)0, this);
189 _dockAreaPopup
->createDeviceWidgets();
190 referenceWidgetForSystray
= _dockAreaPopup
;
192 m_dockWidget
= new KMixDockWidget( this, referenceWidgetForSystray
, _dockAreaPopup
);
193 m_dockWidget
->show();
197 void KMixWindow::saveConfig()
203 #warn We must Sync here, or we will lose configuration data. The reson for that is unknown.
205 KGlobal::config()->sync();
208 void KMixWindow::saveBaseConfig()
210 KConfigGroup
config(KGlobal::config(), "Global");
212 config
.writeEntry( "Size", size() );
213 config
.writeEntry( "Position", pos() );
214 // Cannot use isVisible() here, as in the "aboutToQuit()" case this widget is already hidden.
215 // (Please note that the problem was only there when quitting via Systray - esken).
216 // Using it again, as internal behaviour has changed with KDE4
217 config
.writeEntry( "Visible", isVisible() );
218 config
.writeEntry( "Menubar", _actionShowMenubar
->isChecked() );
219 config
.writeEntry( "AllowDocking", m_showDockWidget
);
220 config
.writeEntry( "TrayVolumeControl", m_volumeWidget
);
221 config
.writeEntry( "Tickmarks", m_showTicks
);
222 config
.writeEntry( "Labels", m_showLabels
);
223 config
.writeEntry( "startkdeRestore", m_onLogin
);
224 Mixer
* mixerMasterCard
= Mixer::getGlobalMasterMixer();
225 if ( mixerMasterCard
!= 0 ) {
226 config
.writeEntry( "MasterMixer", mixerMasterCard
->id() );
228 MixDevice
* mdMaster
= Mixer::getGlobalMasterMD();
229 if ( mdMaster
!= 0 ) {
230 config
.writeEntry( "MasterMixerDevice", mdMaster
->id() );
232 QString mixerIgnoreExpression
= MixerToolBox::instance()->mixerIgnoreExpression();
233 config
.writeEntry( "MixerIgnoreExpression", mixerIgnoreExpression
);
235 // @todo basically this should be moved in the views later (e.g. KDE4.2 ?)
236 if ( m_toplevelOrientation
== Qt::Horizontal
)
237 config
.writeEntry( "Orientation","Horizontal" );
239 config
.writeEntry( "Orientation","Vertical" );
242 void KMixWindow::saveViewConfig()
245 for ( int i
=0; i
<m_wsMixers
->count() ; ++i
)
247 QWidget
*w
= m_wsMixers
->widget(i
);
248 if ( w
->inherits("KMixerWidget") ) {
249 KMixerWidget
* mw
= (KMixerWidget
*)w
;
250 // Here also Views are saved. even for Mixers that are closed. This is neccesary when unplugging cards.
251 // Otherwise the user will be confused afer re-plugging the card (as the config was not saved).
252 mw
->saveConfig( KGlobal::config().data() );
259 * Stores the volumes of all mixers Can be restored via loadVolumes() or
260 * the kmixctrl application.
262 void KMixWindow::saveVolumes()
264 KConfig
*cfg
= new KConfig( "kmixctrlrc" );
265 for ( int i
=0; i
<Mixer::mixers().count(); ++i
)
267 Mixer
*mixer
= (Mixer::mixers())[i
];
268 if ( mixer
->isOpen() ) { // protect from unplugged devices (better do *not* save them)
269 mixer
->volumeSave( cfg
);
277 void KMixWindow::loadConfig()
280 //loadViewConfig(); // mw->loadConfig() explicitly called always after creating mw.
281 //loadVolumes(); // not in use
284 void KMixWindow::loadBaseConfig()
286 KConfigGroup
config(KGlobal::config(), "Global");
288 m_showDockWidget
= config
.readEntry("AllowDocking", true);
289 m_volumeWidget
= config
.readEntry("TrayVolumeControl", true);
290 m_showTicks
= config
.readEntry("Tickmarks", true);
291 m_showLabels
= config
.readEntry("Labels", true);
292 m_onLogin
= config
.readEntry("startkdeRestore", true );
293 m_startVisible
= config
.readEntry("Visible", true);
294 m_multiDriverMode
= config
.readEntry("MultiDriver", false);
295 const QString
& orientationString
= config
.readEntry("Orientation", "Vertical");
296 QString mixerMasterCard
= config
.readEntry( "MasterMixer", "" );
297 QString masterDev
= config
.readEntry( "MasterMixerDevice", "" );
298 //if ( ! mixerMasterCard.isEmpty() && ! masterDev.isEmpty() ) {
299 Mixer::setGlobalMaster(mixerMasterCard
, masterDev
);
301 QString mixerIgnoreExpression
= config
.readEntry( "MixerIgnoreExpression", "Modem" );
302 MixerToolBox::instance()->setMixerIgnoreExpression(mixerIgnoreExpression
);
304 if ( orientationString
== "Horizontal" )
305 m_toplevelOrientation
= Qt::Horizontal
;
307 m_toplevelOrientation
= Qt::Vertical
;
309 // show/hide menu bar
310 bool showMenubar
= config
.readEntry("Menubar", true);
312 if (_actionShowMenubar
) _actionShowMenubar
->setChecked( showMenubar
);
314 // restore window size and position
315 if ( !kapp
->isSessionRestored() ) // done by the session manager otherwise
317 QSize
defSize( minimumWidth(), height() );
318 QSize size
= config
.readEntry("Size", defSize
);
319 if(!size
.isEmpty()) resize(size
);
321 QPoint defPos
= pos();
322 QPoint pos
= config
.readEntry("Position", defPos
);
328 * Loads the volumes of all mixers from kmixctrlrc.
330 * Restores the default voumes as stored via saveVolumes() or the
331 * execution of "kmixctrl --save"
333 /* Currently this is not in use
335 KMixWindow::loadVolumes()
337 KConfig *cfg = new KConfig( "kmixctrlrc", true );
338 for ( int i=0; i<Mixer::mixers().count(); ++i)
340 Mixer *mixer = (Mixer::mixers())[i];
341 mixer->volumeLoad( cfg );
351 * Create or recreate the Mixer GUI elements
353 void KMixWindow::recreateGUI()
355 saveViewConfig(); // save the state before recreating
357 if ( Mixer::mixers().count() > 0 ) {
358 for (int i
=0; i
<Mixer::mixers().count(); ++i
) {
359 Mixer
*mixer
= (Mixer::mixers())[i
];
360 addMixerWidget(mixer
->id());
362 bool dockingSucceded
= updateDocking();
363 if( !dockingSucceded
&& Mixer::mixers().count() > 0 )
364 show(); // avoid invisible and unaccessible main window
367 // No soundcard found. Do not complain, but sit in the background, and wait for newly plugged soundcards.
368 updateDocking(); // -<- removes the DockIcon
373 void KMixWindow::plugged( const char* driverName
, const QString
& /*udi*/, QString
& dev
)
375 // kDebug(67100) << "Plugged: dev=" << dev << "(" << driverName << ") udi=" << udi << "\n";
376 QString driverNameString
;
377 driverNameString
= driverName
;
378 int devNum
= dev
.toInt();
379 Mixer
*mixer
= new Mixer( driverNameString
, devNum
);
381 kDebug(67100) << "Plugged: dev=" << dev
<< "\n";
382 MixerToolBox::instance()->possiblyAddMixer(mixer
);
386 // Test code for OSD. But OSD is postponed to KDE4.1
387 // OSDWidget* osd = new OSDWidget(0);
388 // osd->volChanged(70, true);
392 void KMixWindow::unplugged( const QString
& udi
)
394 // kDebug(67100) << "Unplugged: udi=" <<udi << "\n";
395 for (int i
=0; i
<Mixer::mixers().count(); ++i
) {
396 Mixer
*mixer
= (Mixer::mixers())[i
];
397 // kDebug(67100) << "Try Match with:" << mixer->udi() << "\n";
398 if (mixer
->udi() == udi
) {
399 kDebug(67100) << "Unplugged Match: Removing udi=" <<udi
<< "\n";
400 //KMixToolBox::notification("MasterFallback", "aaa");
401 bool globalMasterMixerDestroyed
= ( mixer
== Mixer::getGlobalMasterMixer() );
402 // Part 1) Remove Tab
403 for ( int i
=0; i
<m_wsMixers
->count() ; ++i
)
405 QWidget
*w
= m_wsMixers
->widget(i
);
406 KMixerWidget
* kmw
= ::qobject_cast
<KMixerWidget
*>(w
);
407 if ( kmw
&& kmw
->mixer() == mixer
) {
408 kmw
->saveConfig( KGlobal::config().data() );
409 m_wsMixers
->removeTab(i
);
411 i
= -1; // Restart loop from scratch (indices are most likeliy invalidated at removeTab() )
414 MixerToolBox::instance()->removeMixer(mixer
);
415 // Check whether the Global Master disappeared, and select a new one if neccesary
416 MixDevice
* md
= Mixer::getGlobalMasterMD();
417 if ( globalMasterMixerDestroyed
|| md
== 0 ) {
418 // We don't know what the global master should be now.
419 // So lets play stupid, and just select the recommendended master of the first device
420 if ( Mixer::mixers().count() > 0 ) {
421 QString localMaster
= ((Mixer::mixers())[0])->getLocalMasterMD()->id();
422 Mixer::setGlobalMaster( ((Mixer::mixers())[0])->id(), localMaster
);
425 text
= i18n("The soundcard containing the master device was unplugged. Changing to control %1 on card %2",
426 ((Mixer::mixers())[0])->getLocalMasterMD()->readableName(),
427 ((Mixer::mixers())[0])->readableName()
429 KMixToolBox::notification("MasterFallback", text
);
432 if ( Mixer::mixers().count() == 0 ) {
434 text
= i18n("The last soundcard was unplugged.");
435 KMixToolBox::notification("MasterFallback", text
);
446 * Create a widget with an error message
447 * This widget shows an error message like "no mixers detected.
448 void KMixWindow::setErrorMixerWidget()
450 QString s = i18n("Please plug in your soundcard.No soundcard found. Probably you have not set it up or are missing soundcard drivers. Please check your operating system manual for installing your soundcard."); // !! better text
451 m_errorLabel = new QLabel( s,this );
452 m_errorLabel->setAlignment( Qt::AlignCenter );
453 m_errorLabel->setWordWrap(true);
454 m_errorLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
455 m_wsMixers->addTab( m_errorLabel, i18n("No soundcard found") );
459 void KMixWindow::clearMixerWidgets()
461 while ( m_wsMixers
->count() != 0 )
463 QWidget
*mw
= m_wsMixers
->widget(0);
464 m_wsMixers
->removeTab(0);
471 void KMixWindow::addMixerWidget(const QString
& mixer_ID
)
473 // kDebug(67100) << "KMixWindow::addMixerWidget() " << mixer_ID;
474 Mixer
*mixer
= MixerToolBox::instance()->find(mixer_ID
);
477 // kDebug(67100) << "KMixWindow::addMixerWidget() " << mixer_ID << " is being added";
478 ViewBase::ViewFlags vflags
= ViewBase::HasMenuBar
;
479 if ( m_showMenubar
) {
480 vflags
|= ViewBase::MenuBarVisible
;
482 if ( m_toplevelOrientation
== Qt::Vertical
) {
483 vflags
|= ViewBase::Horizontal
;
486 vflags
|= ViewBase::Vertical
;
490 KMixerWidget
*kmw
= new KMixerWidget( mixer
, this, "KMixerWidget", vflags
, actionCollection() );
492 /* A newly added mixer will automatically added at the top
493 * and thus the window title is also set appropriately */
494 bool isFirstTab
= m_wsMixers
->count() == 0;
495 m_wsMixers
->addTab( kmw
, kmw
->mixer()->readableName() );
497 m_wsMixers
->setCurrentWidget(kmw
);
498 //setWindowTitle( kmw->mixer()->readableName() );
501 kmw
->loadConfig( KGlobal::config().data() );
503 kmw
->setTicks( m_showTicks
);
504 kmw
->setLabels( m_showLabels
);
505 kmw
->mixer()->readSetFromHWforceUpdate();
506 } // given mixer exist really
511 bool KMixWindow::queryClose ( )
513 // kDebug(67100) << "queryClose ";
514 if ( m_showDockWidget
&& !kapp
->sessionSaving() )
516 // kDebug(67100) << "don't close";
517 // Hide (don't close and destroy), if docking is enabled. Except when session saving (shutdown) is in process.
522 // Accept the close in all situations, when the user has disabled docking
523 // kDebug(67100) << "close";
528 void KMixWindow::hideOrClose ( )
530 if ( m_showDockWidget
&& m_dockWidget
!= 0) {
531 // we can hide if there is a dock widget
535 // if there is no dock widget, we will quit
540 void KMixWindow::quit()
542 // kDebug(67100) << "quit";
547 void KMixWindow::showSettings()
549 if (!m_prefDlg
->isVisible())
551 // copy actual values to dialog
552 m_prefDlg
->m_dockingChk
->setChecked( m_showDockWidget
);
553 m_prefDlg
->m_volumeChk
->setChecked(m_volumeWidget
);
554 m_prefDlg
->m_volumeChk
->setEnabled( m_showDockWidget
);
555 m_prefDlg
->m_onLogin
->setChecked( m_onLogin
);
557 m_prefDlg
->m_showTicks
->setChecked( m_showTicks
);
558 m_prefDlg
->m_showLabels
->setChecked( m_showLabels
);
559 m_prefDlg
->_rbVertical
->setChecked( m_toplevelOrientation
== Qt::Vertical
);
560 m_prefDlg
->_rbHorizontal
->setChecked( m_toplevelOrientation
== Qt::Horizontal
);
568 void KMixWindow::showHelp()
570 actionCollection()->action( "help_contents" )->trigger();
575 KMixWindow::showAbout()
577 actionCollection()->action( "help_about_app" )->trigger();
582 void KMixWindow::applyPrefs( KMixPrefDlg
*prefDlg
)
584 bool labelsHasChanged
= m_showLabels
^ prefDlg
->m_showLabels
->isChecked();
585 bool ticksHasChanged
= m_showTicks
^ prefDlg
->m_showTicks
->isChecked();
586 bool dockwidgetHasChanged
= m_showDockWidget
^ prefDlg
->m_dockingChk
->isChecked();
587 bool systrayPopupHasChanged
= m_volumeWidget
^ prefDlg
->m_volumeChk
->isChecked();
588 bool toplevelOrientationHasChanged
=
589 ( prefDlg
->_rbVertical
->isChecked() && m_toplevelOrientation
== Qt::Horizontal
)
590 || ( prefDlg
->_rbHorizontal
->isChecked() && m_toplevelOrientation
== Qt::Vertical
);
592 m_showLabels
= prefDlg
->m_showLabels
->isChecked();
593 m_showTicks
= prefDlg
->m_showTicks
->isChecked();
594 m_showDockWidget
= prefDlg
->m_dockingChk
->isChecked();
595 m_volumeWidget
= prefDlg
->m_volumeChk
->isChecked();
596 m_onLogin
= prefDlg
->m_onLogin
->isChecked();
597 if ( prefDlg
->_rbVertical
->isChecked() ) {
598 m_toplevelOrientation
= Qt::Vertical
;
600 else if ( prefDlg
->_rbHorizontal
->isChecked() ) {
601 m_toplevelOrientation
= Qt::Horizontal
;
604 if ( labelsHasChanged
|| ticksHasChanged
|| dockwidgetHasChanged
|| toplevelOrientationHasChanged
|| systrayPopupHasChanged
) {
608 this->repaint(); // make KMix look fast (saveConfig() often uses several seconds)
609 kapp
->processEvents();
614 void KMixWindow::toggleMenuBar()
616 menuBar()->setVisible(_actionShowMenubar
->isChecked());
620 void KMixWindow::showEvent( QShowEvent * )
622 if ( m_visibilityUpdateAllowed )
623 m_isVisible = isVisible();
626 void KMixWindow::hideEvent( QHideEvent * )
628 if ( m_visibilityUpdateAllowed )
630 m_isVisible = isVisible();
634 void KMixWindow::stopVisibilityUpdates()
636 m_visibilityUpdateAllowed = false;
640 void KMixWindow::slotHWInfo()
642 KMessageBox::information( 0, m_hwInfoString
, i18n("Mixer Hardware Information") );
645 void KMixWindow::slotConfigureCurrentView()
647 KMixerWidget
* mw
= (KMixerWidget
*)m_wsMixers
->currentWidget();
649 if (mw
) view
= mw
->currentView();
650 if (view
) view
->configureView();
653 void KMixWindow::newMixerShown(int /*tabIndex*/ ) {
654 KMixerWidget
* mw
= (KMixerWidget
*)m_wsMixers
->currentWidget();
655 Mixer
* mixer
= mw
->mixer();
656 setWindowTitle( mixer
->readableName() );
657 // As switching the tab does NOT mean switching the mixer, we do not need to update dock icon here.
658 // It would lead to unnecesary flickering of the (complete) dock area.