Final polisihing for KDE4:
[kdemultimedia.git] / kmix / kmix.cpp
blob3ee949b15705c791ee44af4f616f87420234e25e
2 /*
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
26 #include <QCheckBox>
27 #include <QLabel>
28 #include <qradiobutton.h>
29 #include <KTabWidget>
31 // include files for KDE
32 #include <kcombobox.h>
33 #include <kiconloader.h>
34 #include <kmessagebox.h>
35 #include <kmenubar.h>
36 #include <klocale.h>
37 #include <kconfig.h>
38 #include <kaction.h>
39 #include <kapplication.h>
40 #include <kstandardaction.h>
41 #include <kmenu.h>
42 #include <khelpmenu.h>
43 #include <kdebug.h>
44 #include <kxmlguifactory.h>
45 #include <kglobal.h>
46 #include <kactioncollection.h>
47 #include <ktoggleaction.h>
49 // KMix
50 #include "mixertoolbox.h"
51 #include "kmix.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
61 /* KMixWindow
62 * Constructs a mixer window (KMix main window)
64 KMixWindow::KMixWindow()
65 : KXmlGuiWindow(0),
66 m_showTicks( true ),
67 m_showMenubar(true),
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
71 m_dockWidget()
72 , _dockAreaPopup(0)
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
80 initWidgets();
81 initPrefDlg();
82 MixerToolBox::instance()->initMixer(m_multiDriverMode, m_hwInfoString);
83 KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance();
84 recreateGUI();
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&) ) );
88 if ( m_startVisible )
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()
97 clearMixerWidgets();
98 MixerToolBox::instance()->deinitMixer();
102 void KMixWindow::initActions()
104 // file menu
105 KStandardAction::quit( this, SLOT(quit()), actionCollection());
107 // settings menu
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 ) );
141 // Widgets layout
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
168 if (m_dockWidget)
170 // If this is called during a master control change, the dock widget is currently active, so we use deleteLater().
171 m_dockWidget->deleteLater();
172 m_dockWidget = 0L;
174 if ( _dockAreaPopup ) {
175 // If this is called during a master control change, we rather play safe by using deleteLater().
176 _dockAreaPopup->deleteLater();
177 _dockAreaPopup = 0L;
180 if ( m_showDockWidget == false || Mixer::mixers().count() == 0 ) {
181 return false;
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();
194 return true;
197 void KMixWindow::saveConfig()
199 saveBaseConfig();
200 saveViewConfig();
201 saveVolumes();
202 #ifdef __GNUC_
203 #warn We must Sync here, or we will lose configuration data. The reson for that is unknown.
204 #endif
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" );
238 else
239 config.writeEntry( "Orientation","Vertical" );
242 void KMixWindow::saveViewConfig()
244 // Save Views
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 );
272 delete cfg;
277 void KMixWindow::loadConfig()
279 loadBaseConfig();
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;
306 else
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);
323 move(pos);
328 * Loads the volumes of all mixers from kmixctrlrc.
329 * In other words:
330 * Restores the default voumes as stored via saveVolumes() or the
331 * execution of "kmixctrl --save"
333 /* Currently this is not in use
334 void
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 );
343 delete cfg;
351 * Create or recreate the Mixer GUI elements
353 void KMixWindow::recreateGUI()
355 saveViewConfig(); // save the state before recreating
356 clearMixerWidgets();
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
366 else {
367 // No soundcard found. Do not complain, but sit in the background, and wait for newly plugged soundcards.
368 updateDocking(); // -<- removes the DockIcon
369 hide();
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 );
380 if ( mixer != 0 ) {
381 kDebug(67100) << "Plugged: dev=" << dev << "\n";
382 MixerToolBox::instance()->possiblyAddMixer(mixer);
383 recreateGUI();
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);
410 delete kmw;
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);
424 QString text;
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 ) {
433 QString text;
434 text = i18n("The last soundcard was unplugged.");
435 KMixToolBox::notification("MasterFallback", text);
437 recreateGUI();
438 break;
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);
465 delete mw;
471 void KMixWindow::addMixerWidget(const QString& mixer_ID)
473 // kDebug(67100) << "KMixWindow::addMixerWidget() " << mixer_ID;
474 Mixer *mixer = MixerToolBox::instance()->find(mixer_ID);
475 if ( mixer != 0 )
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;
485 else {
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() );
496 if (isFirstTab) {
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.
518 hide();
519 return false;
521 else {
522 // Accept the close in all situations, when the user has disabled docking
523 // kDebug(67100) << "close";
524 return true;
528 void KMixWindow::hideOrClose ( )
530 if ( m_showDockWidget && m_dockWidget != 0) {
531 // we can hide if there is a dock widget
532 hide();
534 else {
535 // if there is no dock widget, we will quit
536 quit();
540 void KMixWindow::quit()
542 // kDebug(67100) << "quit";
543 kapp->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 );
562 // show dialog
563 m_prefDlg->show();
568 void KMixWindow::showHelp()
570 actionCollection()->action( "help_contents" )->trigger();
574 void
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) {
605 recreateGUI();
608 this->repaint(); // make KMix look fast (saveConfig() often uses several seconds)
609 kapp->processEvents();
610 saveConfig();
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();
648 ViewBase* view = 0;
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.
663 #include "kmix.moc"