OSDN Git Service

kmix: replace KProcess with QProcess
[kde/kde-extraapps.git] / kmix / apps / kmix.cpp
1 /*
2  * KMix -- KDE's full featured mini mixer
3  *
4  * Copyright 1996-2014 The KMix authors. Maintainer: Christian Esken <esken@kde.org>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20
21 #include "apps/kmix.h"
22
23 // include files for QT
24 #include <QCheckBox>
25 #include <QLabel>
26 #include <QDesktopWidget>
27 #include <QPushButton>
28 #include <qradiobutton.h>
29 #include <QCursor>
30 #include <QString>
31 #include <QMenuBar>
32 #include <QProcess>
33
34 // include files for KDE
35 #include <KConfigSkeleton>
36 #include <kcombobox.h>
37 #include <kiconloader.h>
38 #include <kmessagebox.h>
39 #include <klocale.h>
40 #include <kconfig.h>
41 #include <kaction.h>
42 #include <kapplication.h>
43 #include <kstandardaction.h>
44 #include <kmenu.h>
45 #include <khelpmenu.h>
46 #include <kdebug.h>
47 #include <kxmlguifactory.h>
48 #include <kglobal.h>
49 #include <kactioncollection.h>
50 #include <ktoggleaction.h>
51 #include <ksystemeventfilter.h>
52 #include <KTabWidget>
53
54 // KMix
55 #include "gui/guiprofile.h"
56 #include "core/ControlManager.h"
57 #include "core/GlobalConfig.h"
58 #include "core/MasterControl.h"
59 #include "core/MediaController.h"
60 #include "core/mixertoolbox.h"
61 #include "core/kmixdevicemanager.h"
62 #include "gui/kmixerwidget.h"
63 #include "gui/kmixprefdlg.h"
64 #include "gui/kmixdockwidget.h"
65 #include "gui/kmixtoolbox.h"
66 #include "core/version.h"
67 #include "gui/viewdockareapopup.h"
68 #include "gui/dialogaddview.h"
69 #include "gui/dialogselectmaster.h"
70 #include "dbus/dbusmixsetwrapper.h"
71 #include "gui/osdwidget.h"
72
73 #include <X11/Xlib.h>
74 #include <X11/XF86keysym.h>
75 #include <fixx11h.h>
76
77 /* KMixWindow
78  * Constructs a mixer window (KMix main window)
79  */
80
81 static KeyCode s_xf86lowervolume = 0;
82 static KeyCode s_xf86raisevolume = 0;
83
84 KMixWindow::KMixWindow(bool invisible) :
85     KXmlGuiWindow(0, Qt::WindowContextHelpButtonHint),
86     m_multiDriverMode(false), // -<- I never-ever want the multi-drivermode to be activated by accident
87     m_dockWidget(), m_dsm(0), m_dontSetDefaultCardOnStart(false)
88 {
89   setObjectName(QLatin1String("KMixWindow"));
90   // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in
91   setAttribute(Qt::WA_DeleteOnClose, false);
92
93   initActions(); // init actions first, so we can use them in the loadConfig() already
94   loadConfig(); // Load config before initMixer(), e.g. due to "MultiDriver" keyword
95   initActionsLate(); // init actions that require a loaded config
96   KGlobal::locale()->insertCatalog(QLatin1String("kmix-controls"));
97   initWidgets();
98   initPrefDlg();
99   DBusMixSetWrapper::initialize(this, "/Mixers");
100   MixerToolBox::instance()->initMixer(m_multiDriverMode, m_backendFilter,
101       m_hwInfoString);
102   KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance();
103   initActionsAfterInitMixer(); // init actions that require initialized mixer backend(s).
104
105   recreateGUI(false);
106   if (m_wsMixers->count() < 1)
107     {
108       // Something is wrong. Perhaps a hardware or driver or backend change. Let KMix search harder
109       recreateGUI(false, QString(), true);
110     }
111
112   if (!kapp->isSessionRestored() ) // done by the session manager otherwise
113   setInitialSize();
114
115   theKMixDeviceManager->initHotplug();
116   connect(theKMixDeviceManager, SIGNAL(plugged(const char*,QString,QString&)),
117       SLOT (plugged(const char*,QString,QString&)));
118   connect(theKMixDeviceManager, SIGNAL(unplugged(QString)),
119       SLOT (unplugged(QString)));
120   if (m_startVisible && !invisible)
121     show(); // Started visible
122
123   connect(kapp, SIGNAL(aboutToQuit()), SLOT(saveConfig()) );
124
125   ControlManager::instance().addListener(
126           QString(), // All mixers (as the Global master Mixer might change)
127         (ControlChangeType::Type)(ControlChangeType::ControlList | ControlChangeType::MasterChanged),
128         this,
129         QString("KMixWindow")
130         );
131
132   // Send an initial volume refresh (otherwise all volumes are 0 until the next change)
133   ControlManager::instance().announce(QString(), ControlChangeType::Volume, QString("Startup"));
134 }
135
136 KMixWindow::~KMixWindow()
137 {
138   if (m_autouseMultimediaKeys) {
139       XUngrabKey(
140         QX11Info::display(),
141         s_xf86lowervolume,
142         AnyModifier, QX11Info::appRootWindow()
143       );
144
145       XUngrabKey(
146         QX11Info::display(),
147         s_xf86raisevolume,
148         AnyModifier, QX11Info::appRootWindow()
149       );
150   }
151
152   ControlManager::instance().removeListener(this);
153
154   delete m_dsm;
155   delete osdWidget;
156
157   // -1- Cleanup Memory: clearMixerWidgets
158   while (m_wsMixers->count() != 0)
159     {
160       QWidget *mw = m_wsMixers->widget(0);
161       m_wsMixers->removeTab(0);
162       delete mw;
163     }
164
165   // -2- Mixer HW
166   MixerToolBox::instance()->deinitMixer();
167
168   // -3- Action collection (just to please Valgrind)
169   actionCollection()->clear();
170
171   // GUIProfile cache should be cleared very very late, as GUIProfile instances are used in the Views, which
172   // means main window and potentially also in the tray popup (at least we might do so in the future).
173   // This place here could be to early, if we would start to GUIProfile outside KMixWIndow, e.g. in the tray popup.
174   // Until we do so, this is the best place to call clearCache(). Later, e.g. in main() would likely be problematic.
175
176   GUIProfile::clearCache();
177
178
179 }
180
181
182
183 void KMixWindow::controlsChange(int changeType)
184 {
185   ControlChangeType::Type type = ControlChangeType::fromInt(changeType);
186   switch (type )
187   {
188     case ControlChangeType::ControlList:
189     case ControlChangeType::MasterChanged:
190       updateDocking();
191       break;
192
193     default:
194       ControlManager::warnUnexpectedChangeType(type, this);
195       break;
196   }
197
198 }
199
200 void
201 KMixWindow::initActions()
202 {
203   // file menu
204   KStandardAction::quit(this, SLOT(quit()), actionCollection());
205
206   // settings menu
207   _actionShowMenubar = KStandardAction::showMenubar(this, SLOT(toggleMenuBar()),
208       actionCollection());
209   //actionCollection()->addAction( a->objectName(), a );
210   KStandardAction::preferences(this, SLOT(showSettings()), actionCollection());
211   KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()),
212       actionCollection());
213   KAction* action = actionCollection()->addAction("launch_kdesoundsetup");
214   action->setText(i18n("Audio Setup"));
215   connect(action, SIGNAL(triggered(bool)), SLOT(slotKdeAudioSetupExec()));
216
217   action = actionCollection()->addAction("hwinfo");
218   action->setText(i18n("Hardware &Information"));
219   connect(action, SIGNAL(triggered(bool)), SLOT(slotHWInfo()));
220   action = actionCollection()->addAction("hide_kmixwindow");
221   action->setText(i18n("Hide Mixer Window"));
222   connect(action, SIGNAL(triggered(bool)), SLOT(hideOrClose()));
223   action->setShortcut(QKeySequence(Qt::Key_Escape));
224   action = actionCollection()->addAction("toggle_channels_currentview");
225   action->setText(i18n("Configure &Channels..."));
226   connect(action, SIGNAL(triggered(bool)), SLOT(slotConfigureCurrentView()));
227   action = actionCollection()->addAction("select_master");
228   action->setText(i18n("Select Master Channel..."));
229   connect(action, SIGNAL(triggered(bool)), SLOT(slotSelectMaster()));
230
231   action = actionCollection()->addAction("save_1");
232   action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_1));
233   action->setText(i18n("Save volume profile 1"));
234   connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes1()));
235
236   action = actionCollection()->addAction("save_2");
237   action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_2));
238   action->setText(i18n("Save volume profile 2"));
239   connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes2()));
240
241   action = actionCollection()->addAction("save_3");
242   action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_3));
243   action->setText(i18n("Save volume profile 3"));
244   connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes3()));
245
246   action = actionCollection()->addAction("save_4");
247   action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_4));
248   action->setText(i18n("Save volume profile 4"));
249   connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes4()));
250
251   action = actionCollection()->addAction("load_1");
252   action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_1));
253   action->setText(i18n("Load volume profile 1"));
254   connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes1()));
255
256   action = actionCollection()->addAction("load_2");
257   action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_2));
258   action->setText(i18n("Load volume profile 2"));
259   connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes2()));
260
261   action = actionCollection()->addAction("load_3");
262   action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_3));
263   action->setText(i18n("Load volume profile 3"));
264   connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes3()));
265
266   action = actionCollection()->addAction("load_4");
267   action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_4));
268   action->setText(i18n("Load volume profile 4"));
269   connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes4()));
270
271   osdWidget = new OSDWidget();
272
273   createGUI(QLatin1String("kmixui.rc"));
274 }
275
276 void
277 KMixWindow::initActionsLate()
278 {
279   if (m_autouseMultimediaKeys)
280     {
281       KAction* globalAction = actionCollection()->addAction("increase_volume");
282       globalAction->setText(i18n("Increase Volume"));
283       connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotIncreaseVolume()));
284
285       globalAction = actionCollection()->addAction("decrease_volume");
286       globalAction->setText(i18n("Decrease Volume"));
287       connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotDecreaseVolume()));
288
289       globalAction = actionCollection()->addAction("mute");
290       globalAction->setText(i18n("Mute"));
291       connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotMute()));
292
293       s_xf86lowervolume = XKeysymToKeycode(QX11Info::display(), XF86XK_AudioLowerVolume);
294       s_xf86raisevolume = XKeysymToKeycode(QX11Info::display(), XF86XK_AudioRaiseVolume);
295
296       XGrabKey(
297         QX11Info::display(),
298         s_xf86lowervolume,
299         AnyModifier, QX11Info::appRootWindow(), False, GrabModeAsync, GrabModeAsync
300       );
301
302       XGrabKey(
303         QX11Info::display(),
304         s_xf86raisevolume,
305         AnyModifier, QX11Info::appRootWindow(), False, GrabModeAsync, GrabModeAsync
306       );
307
308       KSystemEventFilter::installEventFilter(this);
309     }
310 }
311
312 bool KMixWindow::x11Event(XEvent *xevent)
313 {
314     if (xevent->type == KeyPress) {
315         // qDebug() << Q_FUNC_INFO << xevent << xevent->xkey.keycode;
316         // qDebug() << Q_FUNC_INFO << XF86XK_AudioLowerVolume << s_xf86lowervolume;
317         // qDebug() << Q_FUNC_INFO << XF86XK_AudioRaiseVolume << s_xf86raisevolume;
318         if (xevent->xkey.keycode == s_xf86lowervolume) {
319             increaseOrDecreaseVolume(false);
320             return true;
321         } else if (xevent->xkey.keycode == s_xf86raisevolume) {
322             increaseOrDecreaseVolume(true);
323             return true;
324         }
325     }
326     return false;
327 }
328
329 void
330 KMixWindow::initActionsAfterInitMixer()
331 {
332     QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new", KIconLoader::Toolbar, KIconLoader::SizeSmall);
333     QPushButton* _cornerLabelNew = new QPushButton();
334     _cornerLabelNew->setIcon(cornerNewPM);
335     //cornerLabelNew->setSizePolicy(QSizePolicy());
336     m_wsMixers->setCornerWidget(_cornerLabelNew, Qt::TopLeftCorner);
337     connect(_cornerLabelNew, SIGNAL(clicked()), SLOT (newView()));
338 }
339
340 void
341 KMixWindow::initPrefDlg()
342 {
343     KMixPrefDlg* prefDlg = KMixPrefDlg::createInstance(this, GlobalConfig::instance());
344     connect(prefDlg, SIGNAL(kmixConfigHasChanged()), SLOT(applyPrefs()));
345 }
346
347 void
348 KMixWindow::initWidgets()
349 {
350   m_wsMixers = new KTabWidget();
351   m_wsMixers->setDocumentMode(true);
352   setCentralWidget(m_wsMixers);
353   m_wsMixers->setTabsClosable(false);
354   connect(m_wsMixers, SIGNAL(tabCloseRequested(int)),
355       SLOT(saveAndCloseView(int)));
356
357   QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new",
358       KIconLoader::Toolbar, KIconLoader::SizeSmall);
359
360   connect(m_wsMixers, SIGNAL(currentChanged(int)), SLOT(newMixerShown(int)));
361
362   // show menubar if the actions says so (or if the action does not exist)
363   menuBar()->setVisible(
364       (_actionShowMenubar == 0) || _actionShowMenubar->isChecked());
365 }
366
367 void
368 KMixWindow::setInitialSize()
369 {
370   KConfigGroup config(KGlobal::config(), "Global");
371
372   // HACK: QTabWidget will bound its sizeHint to 200x200 unless scrollbuttons
373   // are disabled, so we disable them, get a decent sizehint and enable them
374   // back
375   m_wsMixers->setUsesScrollButtons(false);
376   QSize defSize = sizeHint();
377   m_wsMixers->setUsesScrollButtons(true);
378   QSize size = config.readEntry("Size", defSize);
379   if (!size.isEmpty())
380     resize(size);
381
382   QPoint defPos = pos();
383   QPoint pos = config.readEntry("Position", defPos);
384   move(pos);
385 }
386
387
388 void KMixWindow::removeDock()
389 {
390         if (m_dockWidget)
391         {
392                 m_dockWidget->deleteLater();
393                 m_dockWidget = 0;
394         }
395 }
396
397 /**
398  * Creates or deletes the KMixDockWidget, depending on whether there is a Mixer instance available.
399  *
400  * @returns true, if the docking succeeded. Failure usually means that there
401  *    was no suitable mixer control selected.
402  */
403 bool KMixWindow::updateDocking()
404 {
405         GlobalConfigData& gcd = GlobalConfig::instance().data;
406
407         if ( !gcd.showDockWidget || Mixer::mixers().isEmpty())
408         {
409                 removeDock();
410                 return false;
411         }
412         if (!m_dockWidget)
413         {
414                 m_dockWidget = new KMixDockWidget(this);
415         }
416         return true;
417 }
418 void
419 KMixWindow::saveConfig()
420 {
421   saveBaseConfig();
422   saveViewConfig();
423   saveVolumes();
424 #warning We must Sync here, or we will lose configuration data. The reson for that is unknown.
425
426   // TODO cesken The reason for not writing might be that we have multiple cascaded KConfig objects. I must migrate to KSharedConfig !!!
427   KGlobal::config()->sync();
428   kDebug()
429   << "Saved config ... sync finished";
430 }
431
432 void KMixWindow::saveBaseConfig()
433 {
434         GlobalConfig::instance().writeConfig();
435
436         KConfigGroup config(KGlobal::config(), "Global");
437
438         config.writeEntry("Size", size());
439         config.writeEntry("Position", pos());
440         // Cannot use isVisible() here, as in the "aboutToQuit()" case this widget is already hidden.
441         // (Please note that the problem was only there when quitting via Systray - esken).
442         // Using it again, as internal behaviour has changed with KDE4
443         config.writeEntry("Visible", isVisible());
444         config.writeEntry("Menubar", _actionShowMenubar->isChecked());
445         config.writeEntry("Soundmenu.Mixers", GlobalConfig::instance().getMixersForSoundmenu().toList());
446
447         config.writeEntry("DefaultCardOnStart", m_defaultCardOnStart);
448         config.writeEntry("ConfigVersion", KMIX_CONFIG_VERSION);
449         config.writeEntry("AutoUseMultimediaKeys", m_autouseMultimediaKeys);
450
451         MasterControl& master = Mixer::getGlobalMasterPreferred();
452         if (master.isValid())
453         {
454                 config.writeEntry("MasterMixer", master.getCard());
455                 config.writeEntry("MasterMixerDevice", master.getControl());
456         }
457         QString mixerIgnoreExpression = MixerToolBox::instance()->mixerIgnoreExpression();
458         config.writeEntry("MixerIgnoreExpression", mixerIgnoreExpression);
459
460         kDebug()
461         << "Base configuration saved";
462 }
463
464 void KMixWindow::saveViewConfig()
465 {
466         QMap<QString, QStringList> mixerViews;
467
468         // The following loop is necessary for the case that the user has hidden all views for a Mixer instance.
469         // Otherwise we would not save the Meta information (step -2- below for that mixer.
470         // We also do not save dynamic mixers (e.g. PulseAudio)
471         foreach ( Mixer* mixer, Mixer::mixers() )
472         {
473                 if ( !mixer->isDynamic() )
474                 {
475                         mixerViews[mixer->id()]; // just insert a map entry
476                 }
477         }
478
479         // -1- Save the views themselves
480         for (int i = 0; i < m_wsMixers->count(); ++i)
481         {
482                 QWidget *w = m_wsMixers->widget(i);
483                 if (w->inherits("KMixerWidget"))
484                 {
485                         KMixerWidget* mw = (KMixerWidget*) w;
486                         // Here also Views are saved. even for Mixers that are closed. This is necessary when unplugging cards.
487                         // Otherwise the user will be confused afer re-plugging the card (as the config was not saved).
488                         mw->saveConfig(KGlobal::config().data());
489                         // add the view to the corresponding mixer list, so we can save a views-per-mixer list below
490                         if (!mw->mixer()->isDynamic())
491                         {
492                                 QStringList& qsl = mixerViews[mw->mixer()->id()];
493                                 qsl.append(mw->getGuiprof()->getId());
494                         }
495                 }
496         }
497
498         // -2- Save Meta-Information (which views, and in which order). views-per-mixer list
499         KConfigGroup pconfig(KGlobal::config(), "Profiles");
500         QMap<QString, QStringList>::const_iterator itEnd = mixerViews.constEnd();
501         for (QMap<QString, QStringList>::const_iterator it = mixerViews.constBegin(); it != itEnd; ++it)
502         {
503                 const QString& mixerProfileKey = it.key(); // this is actually some mixer->id()
504                 const QStringList& qslProfiles = it.value();
505                 pconfig.writeEntry(mixerProfileKey, qslProfiles);
506                 kDebug()
507                 << "Save Profile List for " << mixerProfileKey << ", number of views is " << qslProfiles.count();
508         }
509
510         kDebug()
511         << "View configuration saved";
512 }
513
514 /**
515  * Stores the volumes of all mixers  Can be restored via loadVolumes() or
516  * the kmixctrl application.
517  */
518 void
519 KMixWindow::saveVolumes()
520 {
521   saveVolumes(QString());
522 }
523
524 void
525 KMixWindow::saveVolumes(QString postfix)
526 {
527   const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix);
528   KConfig *cfg = new KConfig(kmixctrlRcFilename);
529   for (int i = 0; i < Mixer::mixers().count(); ++i)
530     {
531       Mixer *mixer = (Mixer::mixers())[i];
532       if (mixer->isOpen())
533         { // protect from unplugged devices (better do *not* save them)
534           mixer->volumeSave(cfg);
535         }
536     }
537   cfg->sync();
538   delete cfg;
539   kDebug()
540   << "Volume configuration saved";
541 }
542
543 QString
544 KMixWindow::getKmixctrlRcFilename(QString postfix)
545 {
546   QString kmixctrlRcFilename("kmixctrlrc");
547   if (!postfix.isEmpty())
548     {
549       kmixctrlRcFilename.append(".").append(postfix);
550     }
551   return kmixctrlRcFilename;
552 }
553
554 void
555 KMixWindow::loadConfig()
556 {
557   loadBaseConfig();
558
559   //loadViewConfig(); // mw->loadConfig() explicitly called always after creating mw.
560   //loadVolumes(); // not in use
561
562   // create an initial snapshot, so we have a reference of the state before changes through the preferences dialog
563   configDataSnapshot = GlobalConfig::instance().data;
564 }
565
566 void
567 KMixWindow::loadBaseConfig()
568 {
569   KConfigGroup config(KGlobal::config(), "Global");
570
571   QList<QString> preferredMixersInSoundMenu;
572   preferredMixersInSoundMenu = config.readEntry("Soundmenu.Mixers", preferredMixersInSoundMenu);
573   GlobalConfig::instance().setMixersForSoundmenu(preferredMixersInSoundMenu.toSet());
574
575   m_startVisible = config.readEntry("Visible", false);
576   m_multiDriverMode = config.readEntry("MultiDriver", false);
577   m_defaultCardOnStart = config.readEntry("DefaultCardOnStart", "");
578   m_autouseMultimediaKeys = config.readEntry("AutoUseMultimediaKeys", true);
579   QString mixerMasterCard = config.readEntry("MasterMixer", "");
580   QString masterDev = config.readEntry("MasterMixerDevice", "");
581   Mixer::setGlobalMaster(mixerMasterCard, masterDev, true);
582   QString mixerIgnoreExpression = config.readEntry("MixerIgnoreExpression",
583       "Modem");
584   MixerToolBox::instance()->setMixerIgnoreExpression(mixerIgnoreExpression);
585
586   // --- Advanced options, without GUI: START -------------------------------------
587   QString volumePercentageStepString = config.readEntry("VolumePercentageStep");
588   if (!volumePercentageStepString.isNull())
589     {
590       float volumePercentageStep = volumePercentageStepString.toFloat();
591       if (volumePercentageStep > 0 && volumePercentageStep <= 100)
592         Volume::VOLUME_STEP_DIVISOR = (100 / volumePercentageStep);
593     }
594
595   // --- Advanced options, without GUI: END -------------------------------------
596
597   m_backendFilter = config.readEntry<>("Backends", QList<QString>());
598   kDebug() << "Backends: " << m_backendFilter;
599
600   // show/hide menu bar
601   bool showMenubar = config.readEntry("Menubar", true);
602
603   if (_actionShowMenubar)
604     _actionShowMenubar->setChecked(showMenubar);
605 }
606
607 /**
608  * Loads the volumes of all mixers from kmixctrlrc.
609  * In other words:
610  * Restores the default voumes as stored via saveVolumes() or the
611  * execution of "kmixctrl --save"
612  */
613
614 void
615 KMixWindow::loadVolumes()
616 {
617   loadVolumes(QString());
618 }
619
620 void
621 KMixWindow::loadVolumes(QString postfix)
622 {
623   kDebug()
624   << "About to load config (Volume)";
625   const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix);
626
627   KConfig *cfg = new KConfig(kmixctrlRcFilename);
628   for (int i = 0; i < Mixer::mixers().count(); ++i)
629     {
630       Mixer *mixer = (Mixer::mixers())[i];
631       mixer->volumeLoad(cfg);
632     }
633   delete cfg;
634 }
635
636
637 void
638 KMixWindow::recreateGUIwithSavingView()
639 {
640 //  saveViewConfig();
641   recreateGUI(true);
642 }
643
644 void
645 KMixWindow::recreateGUI(bool saveConfig)
646 {
647   recreateGUI(saveConfig, QString(), false);
648 }
649
650 /**
651  * Create or recreate the Mixer GUI elements
652  *
653  * @param saveConfig  Whether to save all View configurations before recreating
654  * @param forceNewTab To enforce opening a new tab, even when the profileList in the kmixrc is empty.
655  *                    It should only be set to "true" in case of a Hotplug (because then the user definitely expects a new Tab to show).
656  */
657 void
658 KMixWindow::recreateGUI(bool saveConfig, const QString& mixerId,
659     bool forceNewTab)
660 {
661   // -1- Remember which of the tabs is currently selected for restoration for re-insertion
662   int oldTabPosition = m_wsMixers->currentIndex();
663
664   if (saveConfig)
665     saveViewConfig();  // save the state before recreating
666
667   // -2- RECREATE THE ALREADY EXISTING TABS **********************************
668   QMap<Mixer*, bool> mixerHasProfile;
669
670   // -2a- Build a list of all active profiles in the main window (that means: from all tabs)
671   QList<GUIProfile*> activeGuiProfiles;
672   for (int i = 0; i < m_wsMixers->count(); ++i)
673     {
674       KMixerWidget* kmw = qobject_cast<KMixerWidget*>(m_wsMixers->widget(i));
675       if (kmw)
676         {
677           activeGuiProfiles.append(kmw->getGuiprof());
678         }
679     }
680
681   // TODO The following loop is a bit buggy, as it iterates over all cached Profiles. But that is wrong for Tabs that got closed.
682   //       I need to loop over something else, e.g. a  profile list built from the currently open Tabs.
683   //       Or (if it that is easier) I might discard the Profile from the cache on "close-tab" (but that must also include unplug actions).
684   foreach( GUIProfile* guiprof, activeGuiProfiles){
685   KMixerWidget* kmw = findKMWforTab(guiprof->getId());
686   Mixer *mixer = Mixer::findMixer( guiprof->getMixerId() );
687   if ( mixer == 0 )
688     {
689       kError() << "MixerToolBox::find() hasn't found the Mixer for the profile " << guiprof->getId();
690       continue;
691     }
692   mixerHasProfile[mixer] = true;
693   if ( kmw == 0 )
694     {
695       // does not yet exist => create
696       addMixerWidget(mixer->id(), guiprof->getId(), -1);
697     }
698   else
699     {
700       // did exist => remove and insert new guiprof at old position
701       int indexOfTab = m_wsMixers->indexOf(kmw);
702       if ( indexOfTab != -1 ) m_wsMixers->removeTab(indexOfTab);
703       delete kmw;
704       addMixerWidget(mixer->id(), guiprof->getId(), indexOfTab);
705     }
706 } // Loop over all GUIProfile's
707
708 // -3- ADD TABS FOR Mixer instances that have no tab yet **********************************
709   KConfigGroup pconfig(KGlobal::config(), "Profiles");
710   foreach ( Mixer *mixer, Mixer::mixers()){
711   if ( mixerHasProfile.contains(mixer))
712     {
713       continue;  // OK, this mixer already has a profile => skip it
714     }
715   // No TAB YET => This should mean KMix is just started, or the user has just plugged in a card
716   bool profileListHasKey = false;
717   QStringList profileList;
718   bool aProfileWasAddedSucesufully = false;
719
720   if ( !mixer->isDynamic() )
721     {
722       // We do not support save profiles for dynamic mixers (i.e. PulseAudio)
723
724       profileListHasKey = pconfig.hasKey( mixer->id() );// <<< SHOULD be before the following line
725       profileList = pconfig.readEntry( mixer->id(), QStringList() );
726
727       foreach ( QString profileId, profileList)
728         {
729           // This handles the profileList form the kmixrc
730           kDebug() << "Now searching for profile: " << profileId;
731           GUIProfile* guiprof = GUIProfile::find(mixer, profileId, true, false);// ### Card specific profile ###
732           if ( guiprof != 0 )
733             {
734               addMixerWidget(mixer->id(), guiprof->getId(), -1);
735               aProfileWasAddedSucesufully = true;
736             }
737           else
738             {
739               kError() << "Cannot load profile " << profileId << " . It was removed by the user, or the KMix config file is defective.";
740             }
741         }
742     }
743
744   // The we_need_a_fallback case is a bit tricky. Please ask the author (cesken) before even considering to change the code.
745   bool we_need_a_fallback = !aProfileWasAddedSucesufully;// we *possibly* want a fallback, if we couldn't add one
746   bool thisMixerShouldBeForced = forceNewTab && ( mixerId.isEmpty() || (mixer->id() == mixerId) );
747   we_need_a_fallback = we_need_a_fallback && ( thisMixerShouldBeForced || !profileListHasKey );// Additional requirement: "forced-tab-for-this-mixer" OR "no key stored in kmixrc yet"
748   if ( we_need_a_fallback )
749     {
750       // The profileList was empty or nothing could be loaded
751       //     (Hint: This means the user cannot hide a device completely
752
753       // Lets try a bunch of fallback strategies:
754       GUIProfile* guiprof = 0;
755       if ( !mixer->isDynamic() )
756         {
757           // We know that GUIProfile::find() will return 0 if the mixer is dynamic, so don't bother checking.
758           kDebug() << "Attempting to find a card-specific GUI Profile for the mixer " << mixer->id();
759           guiprof = GUIProfile::find(mixer, QString("default"), false, false);// ### Card specific profile ###
760           if ( guiprof == 0 )
761             {
762               kDebug() << "Not found. Attempting to find a generic GUI Profile for the mixer " << mixer->id();
763               guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ###
764             }
765         }
766       if ( guiprof == 0)
767         {
768           kDebug() << "Using fallback GUI Profile for the mixer " << mixer->id();
769           // This means there is neither card specific nor card unspecific profile
770           // This is the case for some backends (as they don't ship profiles).
771           guiprof = GUIProfile::fallbackProfile(mixer);
772         }
773
774       if ( guiprof != 0 )
775         {
776           guiprof->setDirty();  // All fallback => dirty
777           addMixerWidget(mixer->id(), guiprof->getId(), -1);
778         }
779       else
780         {
781           kError() << "Cannot use ANY profile (including Fallback) for mixer " << mixer->id() << " . This is impossible, and thus this mixer can NOT be used.";
782         }
783
784     }
785 }
786   mixerHasProfile.clear();
787
788   // -4- FINALIZE **********************************
789   if (m_wsMixers->count() > 0)
790     {
791       if (oldTabPosition >= 0)
792         {
793           m_wsMixers->setCurrentIndex(oldTabPosition);
794         }
795       bool dockingSucceded = updateDocking();
796       if (!dockingSucceded && !Mixer::mixers().empty())
797         {
798           show(); // avoid invisible and unaccessible main window
799         }
800     }
801   else
802     {
803       // No soundcard found. Do not complain, but sit in the background, and wait for newly plugged soundcards.
804       updateDocking();  // -<- removes the DockIcon
805       hide();
806     }
807
808 }
809
810 KMixerWidget*
811 KMixWindow::findKMWforTab(const QString& kmwId)
812 {
813   for (int i = 0; i < m_wsMixers->count(); ++i)
814     {
815       KMixerWidget* kmw = (KMixerWidget*) m_wsMixers->widget(i);
816       if (kmw->getGuiprof()->getId() == kmwId)
817         {
818           return kmw;
819         }
820     }
821   return 0;
822 }
823
824 void
825 KMixWindow::newView()
826 {
827   if (Mixer::mixers().empty())
828     {
829       kError() << "Trying to create a View, but no Mixer exists";
830       return; // should never happen
831     }
832
833   Mixer *mixer = Mixer::mixers()[0];
834   QPointer<DialogAddView> dav = new DialogAddView(this, mixer);
835   int ret = dav->exec();
836
837   if (QDialog::Accepted == ret)
838     {
839       QString profileName = dav->getresultViewName();
840       QString mixerId = dav->getresultMixerId();
841       mixer = Mixer::findMixer(mixerId);
842       kDebug()
843       << ">>> mixer = " << mixerId << " -> " << mixer;
844
845       GUIProfile* guiprof = GUIProfile::find(mixer, profileName, false, false);
846       if (guiprof == 0)
847         {
848           guiprof = GUIProfile::find(mixer, profileName, false, true);
849         }
850
851       if (guiprof == 0)
852         {
853           static const QString msg(
854               i18n("Cannot add view - GUIProfile is invalid."));
855           errorPopup(msg);
856         }
857       else
858         {
859           bool ret = addMixerWidget(mixer->id(), guiprof->getId(), -1);
860           if (ret == false)
861             {
862               errorPopup(i18n("View already exists. Cannot add View."));
863             }
864         }
865
866       delete dav;
867     }
868
869   //kDebug() << "Exit";
870 }
871
872 /**
873  * Save the view and close it
874  *
875  * @arg idx The index in the TabWidget
876  */
877 void
878 KMixWindow::saveAndCloseView(int idx)
879 {
880   kDebug()
881   << "Enter";
882   QWidget *w = m_wsMixers->widget(idx);
883   KMixerWidget* kmw = ::qobject_cast<KMixerWidget*>(w);
884   if (kmw)
885     {
886       kmw->saveConfig(KGlobal::config().data()); // -<- This alone is not enough, as I need to save the META information as well. Thus use saveViewConfig() below
887       m_wsMixers->removeTab(idx);
888       updateTabsClosable();
889       saveViewConfig();
890       delete kmw;
891     }
892   kDebug()
893   << "Exit";
894 }
895
896 void
897 KMixWindow::plugged(const char* driverName, const QString& udi, QString& dev)
898 {
899   kDebug()
900   << "Plugged: dev=" << dev << "(" << driverName << ") udi=" << udi << "\n";
901   QString driverNameString;
902   driverNameString = driverName;
903   int devNum = dev.toInt();
904   Mixer *mixer = new Mixer(driverNameString, devNum);
905   if (mixer != 0)
906     {
907       kDebug()
908       << "Plugged: dev=" << dev << "\n";
909       MixerToolBox::instance()->possiblyAddMixer(mixer);
910       recreateGUI(true, mixer->id(), true);
911     }
912 }
913
914 void
915 KMixWindow::unplugged(const QString& udi)
916 {
917   kDebug()
918   << "Unplugged: udi=" << udi << "\n";
919   for (int i = 0; i < Mixer::mixers().count(); ++i)
920     {
921       Mixer *mixer = (Mixer::mixers())[i];
922       //         kDebug(67100) << "Try Match with:" << mixer->udi() << "\n";
923       if (mixer->udi() == udi)
924         {
925           kDebug() << "Unplugged Match: Removing udi=" << udi << "\n";
926           //KMixToolBox::notification("MasterFallback", "aaa");
927           bool globalMasterMixerDestroyed = (mixer == Mixer::getGlobalMasterMixer());
928           // Part 1) Remove Tab
929           for (int i = 0; i < m_wsMixers->count(); ++i)
930             {
931               QWidget *w = m_wsMixers->widget(i);
932               KMixerWidget* kmw = ::qobject_cast<KMixerWidget*>(w);
933               if (kmw && kmw->mixer() == mixer)
934                 {
935                   saveAndCloseView(i);
936                   i = -1; // Restart loop from scratch (indices are most likely invalidated at removeTab() )
937                 }
938             }
939           MixerToolBox::instance()->removeMixer(mixer);
940           // Check whether the Global Master disappeared, and select a new one if necessary
941           std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
942           if (globalMasterMixerDestroyed || md.get() == 0)
943             {
944               // We don't know what the global master should be now.
945               // So lets play stupid, and just select the recommended master of the first device
946               if (Mixer::mixers().count() > 0)
947                 {
948                   std::shared_ptr<MixDevice> master =
949                       ((Mixer::mixers())[0])->getLocalMasterMD();
950                   if (master.get() != 0)
951                     {
952                       QString localMaster = master->id();
953                       Mixer::setGlobalMaster(((Mixer::mixers())[0])->id(), localMaster, false);
954
955                       QString text;
956                       text =
957                           i18n(
958                               "The soundcard containing the master device was unplugged. Changing to control %1 on card %2.",
959                               master->readableName(),
960                               ((Mixer::mixers())[0])->readableName());
961                       KMixToolBox::notification("MasterFallback", text);
962                     }
963                 }
964             }
965           if (Mixer::mixers().count() == 0)
966             {
967               QString text;
968               text = i18n("The last soundcard was unplugged.");
969               KMixToolBox::notification("MasterFallback", text);
970             }
971           recreateGUI(true);
972           break;
973         }
974     }
975
976 }
977
978 /**
979  * Create a widget with an error message
980  * This widget shows an error message like "no mixers detected.
981  void KMixWindow::setErrorMixerWidget()
982  {
983  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
984  m_errorLabel = new QLabel( s,this  );
985  m_errorLabel->setAlignment( Qt::AlignCenter );
986  m_errorLabel->setWordWrap(true);
987  m_errorLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
988  m_wsMixers->addTab( m_errorLabel, i18n("No soundcard found") );
989  }
990  */
991
992 /**
993  *
994  */
995 bool
996 KMixWindow::profileExists(QString guiProfileId)
997 {
998   for (int i = 0; i < m_wsMixers->count(); ++i)
999     {
1000       KMixerWidget* kmw = qobject_cast<KMixerWidget*>(m_wsMixers->widget(i));
1001       if (kmw && kmw->getGuiprof()->getId() == guiProfileId)
1002         return true;
1003     }
1004   return false;
1005 }
1006
1007 bool
1008 KMixWindow::addMixerWidget(const QString& mixer_ID, QString guiprofId, int insertPosition)
1009 {
1010   kDebug() << "Add " << guiprofId;
1011   GUIProfile* guiprof = GUIProfile::find(guiprofId);
1012   if (guiprof != 0 && profileExists(guiprof->getId())) // TODO Bad place. Should be checked in the add-tab-dialog
1013     return false; // already present => don't add again
1014   Mixer *mixer = Mixer::findMixer(mixer_ID);
1015   if (mixer == 0)
1016     return false; // no such Mixer
1017
1018   //       kDebug(67100) << "KMixWindow::addMixerWidget() " << mixer_ID << " is being added";
1019   ViewBase::ViewFlags vflags = ViewBase::HasMenuBar;
1020   if ((_actionShowMenubar == 0) || _actionShowMenubar->isChecked())
1021     vflags |= ViewBase::MenuBarVisible;
1022   if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Vertical)
1023     vflags |= ViewBase::Horizontal;
1024   else
1025     vflags |= ViewBase::Vertical;
1026
1027   KMixerWidget *kmw = new KMixerWidget(mixer, this, vflags, guiprofId,
1028       actionCollection());
1029   /* A newly added mixer will automatically added at the top
1030    * and thus the window title is also set appropriately */
1031
1032   /*
1033    * Skip the name from the profile for now. I would at least have to do the '&' quoting for the tab label. But I am
1034    * also not 100% sure whether the current name from the profile is any good - it does (likely) not even contain the
1035    * card ID. This means you cannot distinguish between cards with an identical name.
1036    */
1037 //  QString tabLabel = guiprof->getName();
1038 //  if (tabLabel.isEmpty())
1039 //        QString tabLabel = kmw->mixer()->readableName(true);
1040
1041   QString tabLabel = kmw->mixer()->readableName(true);
1042
1043   m_dontSetDefaultCardOnStart = true; // inhibit implicit setting of m_defaultCardOnStart
1044
1045   if (insertPosition == -1)
1046     m_wsMixers->addTab(kmw, tabLabel);
1047   else
1048     m_wsMixers->insertTab(insertPosition, kmw, tabLabel);
1049
1050   if (kmw->getGuiprof()->getId() == m_defaultCardOnStart)
1051     {
1052       m_wsMixers->setCurrentWidget(kmw);
1053     }
1054
1055   updateTabsClosable();
1056   m_dontSetDefaultCardOnStart = false;
1057
1058   kmw->loadConfig(KGlobal::config().data());
1059   // Now force to read for new tabs, especially after hotplug. Note: Doing it here is bad design and possibly
1060   // obsolete, as the backend should take care of upating itself.
1061   kmw->mixer()->readSetFromHWforceUpdate();
1062   return true;
1063 }
1064
1065 void KMixWindow::updateTabsClosable()
1066 {
1067     // Ddo not allow to close the last view
1068     m_wsMixers->setTabsClosable(m_wsMixers->count() > 1);
1069 }
1070
1071 bool KMixWindow::queryClose()
1072 {
1073         GlobalConfigData& gcd = GlobalConfig::instance().data;
1074         if (gcd.showDockWidget && !kapp->sessionSaving() )
1075         {
1076                 // Hide (don't close and destroy), if docking is enabled. Except when session saving (shutdown) is in process.
1077                 hide();
1078                 return false;
1079         }
1080         else
1081         {
1082                 // Accept the close, if:
1083                 //     The user has disabled docking
1084                 // or  SessionSaving() is running
1085                 //         kDebug(67100) << "close";
1086                 return true;
1087         }
1088 }
1089
1090 void KMixWindow::hideOrClose()
1091 {
1092         GlobalConfigData& gcd = GlobalConfig::instance().data;
1093         if (gcd.showDockWidget && m_dockWidget != 0)
1094         {
1095                 // we can hide if there is a dock widget
1096                 hide();
1097         }
1098         else
1099         {
1100                 //  if there is no dock widget, we will quit
1101                 quit();
1102         }
1103 }
1104
1105 // internal helper to prevent code duplication in slotIncreaseVolume and slotDecreaseVolume
1106 void
1107 KMixWindow::increaseOrDecreaseVolume(bool increase)
1108 {
1109   Mixer* mixer = Mixer::getGlobalMasterMixer(); // only needed for the awkward construct below
1110   if (mixer == 0)
1111     return; // e.g. when no soundcard is available
1112   std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1113   if (md.get() == 0)
1114     return; // shouldn't happen, but lets play safe
1115
1116   Volume::VolumeTypeFlag volumeType = md->playbackVolume().hasVolume() ? Volume::Playback : Volume::Capture;
1117   md->increaseOrDecreaseVolume(!increase, volumeType);
1118   md->mixer()->commitVolumeChange(md);
1119
1120   showVolumeDisplay();
1121 }
1122
1123 void
1124 KMixWindow::slotIncreaseVolume()
1125 {
1126   increaseOrDecreaseVolume(true);
1127 }
1128
1129 void
1130 KMixWindow::slotDecreaseVolume()
1131 {
1132   increaseOrDecreaseVolume(false);
1133 }
1134
1135 void
1136 KMixWindow::showVolumeDisplay()
1137 {
1138   Mixer* mixer = Mixer::getGlobalMasterMixer();
1139   if (mixer == 0)
1140     return; // e.g. when no soundcard is available
1141   std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1142   if (md.get() == 0)
1143     return; // shouldn't happen, but lets play safe
1144     
1145 // Current volume
1146  // Setting not required any more, as the OSD updates the volume level itself
1147 //   Volume& vol = md->playbackVolume();
1148 //   osdWidget->setCurrentVolume(vol.getAvgVolumePercent(Volume::MALL),
1149 //       md->isMuted());
1150         if (GlobalConfig::instance().data.showOSD)
1151         {
1152                 osdWidget->show();
1153                 osdWidget->activateOSD(); //Enable the hide timer
1154         }
1155   //Center the OSD
1156   QRect rect = KApplication::kApplication()->desktop()->screenGeometry(
1157       QCursor::pos());
1158   QSize size = osdWidget->sizeHint();
1159   int posX = rect.x() + (rect.width() - size.width()) / 2;
1160   int posY = rect.y() + 4 * rect.height() / 5;
1161   osdWidget->setGeometry(posX, posY, size.width(), size.height());
1162 }
1163
1164 /**
1165  * Mutes the global master. (SLOT)
1166  */
1167 void KMixWindow::slotMute()
1168 {
1169         Mixer* mixer = Mixer::getGlobalMasterMixer();
1170         if (mixer == 0)
1171                 return; // e.g. when no soundcard is available
1172         std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1173         if (md.get() == 0)
1174                 return; // shouldn't happen, but lets play safe
1175         md->toggleMute();
1176         mixer->commitVolumeChange(md);
1177         showVolumeDisplay();
1178 }
1179
1180 void
1181 KMixWindow::quit()
1182 {
1183   //     kDebug(67100) << "quit";
1184   kapp->quit();
1185 }
1186
1187 /**
1188  * Shows the configuration dialog, with the "general" tab opened.
1189  */
1190 void KMixWindow::showSettings()
1191 {
1192         KMixPrefDlg::getInstance()->switchToPage(KMixPrefDlg::PrefGeneral);
1193         KMixPrefDlg::getInstance()->show();
1194 }
1195
1196 void
1197 KMixWindow::showHelp()
1198 {
1199   actionCollection()->action("help_contents")->trigger();
1200 }
1201
1202 void
1203 KMixWindow::showAbout()
1204 {
1205   actionCollection()->action("help_about_app")->trigger();
1206 }
1207
1208 /**
1209  * Apply the Preferences from the preferences dialog. Depending on what has been changed,
1210  * the corresponding announcemnts are made.
1211  */
1212 void KMixWindow::applyPrefs()
1213 {
1214         // -1- Determine what has changed ------------------------------------------------------------------
1215         GlobalConfigData& config = GlobalConfig::instance().data;
1216         GlobalConfigData& configBefore = configDataSnapshot;
1217
1218         bool labelsHasChanged = config.showLabels ^ configBefore.showLabels;
1219         bool ticksHasChanged = config.showTicks ^ configBefore.showTicks;
1220
1221         bool dockwidgetHasChanged = config.showDockWidget ^ configBefore.showDockWidget;
1222
1223         bool toplevelOrientationHasChanged = config.getToplevelOrientation() != configBefore.getToplevelOrientation();
1224         bool traypopupOrientationHasChanged = config.getTraypopupOrientation() != configBefore.getTraypopupOrientation();
1225         kDebug() << "toplevelOrientationHasChanged=" << toplevelOrientationHasChanged <<
1226                 ", config=" << config.getToplevelOrientation() << ", configBefore=" << configBefore.getToplevelOrientation();
1227         kDebug() << "trayOrientationHasChanged=" << traypopupOrientationHasChanged <<
1228                 ", config=" << config.getTraypopupOrientation() << ", configBefore=" << configBefore.getTraypopupOrientation();
1229
1230         // -2- Determine what effect the changes have ------------------------------------------------------------------
1231
1232         if (dockwidgetHasChanged || toplevelOrientationHasChanged
1233                 || traypopupOrientationHasChanged)
1234         {
1235                 // These might need a complete relayout => announce a ControlList change to rebuild everything
1236                 ControlManager::instance().announce(QString(), ControlChangeType::ControlList, QString("Preferences Dialog"));
1237         }
1238         else if (labelsHasChanged || ticksHasChanged)
1239         {
1240                 ControlManager::instance().announce(QString(), ControlChangeType::GUI, QString("Preferences Dialog"));
1241         }
1242         // showOSD does not require any information. It reads on-the-fly from GlobalConfig.
1243
1244
1245         // -3- Apply all changes ------------------------------------------------------------------
1246
1247 //      this->repaint(); // make KMix look fast (saveConfig() often uses several seconds)
1248         kapp->processEvents();
1249
1250         configDataSnapshot = GlobalConfig::instance().data; // create a new snapshot as all current changes are applied now
1251
1252         // Remove saveConfig() IF aa changes have been migrated to GlobalConfig.
1253         // Currently there is still stuff like "show menu bar".
1254         saveConfig();
1255 }
1256
1257 void
1258 KMixWindow::toggleMenuBar()
1259 {
1260   menuBar()->setVisible(_actionShowMenubar->isChecked());
1261 }
1262
1263 void
1264 KMixWindow::slotHWInfo()
1265 {
1266   KMessageBox::information(0, m_hwInfoString,
1267       i18n("Mixer Hardware Information"));
1268 }
1269
1270 void
1271 KMixWindow::slotKdeAudioSetupExec()
1272 {
1273   static const QString kcmshell4 = QString::fromLatin1("kcmshell4");
1274   static const QStringList kcmshell4args = QStringList() << QString::fromLatin1("kcmplayer");
1275   if (!QProcess::startDetached(kcmshell4, kcmshell4args)) {
1276     errorPopup(i18n("The kcmshell4 application is either not installed or not working."));
1277   }
1278 }
1279
1280 void
1281 KMixWindow::errorPopup(const QString& msg)
1282 {
1283   QPointer<KDialog> dialog = new KDialog(this);
1284   dialog->setButtons(KDialog::Ok);
1285   dialog->setCaption(i18n("Error"));
1286   QLabel* qlbl = new QLabel(msg);
1287   dialog->setMainWidget(qlbl);
1288   dialog->exec();
1289   delete dialog;
1290   kWarning() << msg;
1291 }
1292
1293 void
1294 KMixWindow::slotConfigureCurrentView()
1295 {
1296   KMixerWidget* mw = (KMixerWidget*) m_wsMixers->currentWidget();
1297   ViewBase* view = 0;
1298   if (mw)
1299     view = mw->currentView();
1300   if (view)
1301     view->configureView();
1302 }
1303
1304 void KMixWindow::slotSelectMasterClose(QObject*)
1305 {
1306         m_dsm = 0;
1307 }
1308
1309 void KMixWindow::slotSelectMaster()
1310 {
1311         Mixer *mixer = Mixer::getGlobalMasterMixer();
1312         if (mixer != 0)
1313         {
1314                 if (!m_dsm) {
1315                         m_dsm = new DialogSelectMaster(Mixer::getGlobalMasterMixer(), this);
1316                         connect(m_dsm, SIGNAL(destroyed(QObject*)), this, SLOT(slotSelectMasterClose(QObject*)));
1317                         m_dsm->setAttribute(Qt::WA_DeleteOnClose, true);
1318                         m_dsm->show();
1319                 }
1320                 m_dsm->raise();
1321                 m_dsm->activateWindow();
1322         }
1323         else
1324         {
1325                 KMessageBox::error(0, i18n("No sound card is installed or currently plugged in."));
1326         }
1327 }
1328
1329 void
1330 KMixWindow::newMixerShown(int /*tabIndex*/)
1331 {
1332   KMixerWidget* kmw = (KMixerWidget*) m_wsMixers->currentWidget();
1333   if (kmw)
1334     {
1335       // I am using the app name as a PREFIX, as KMix is a single window app, and it is
1336       // more helpful to the user to see "KDE Mixer" in a window list than a possibly cryptic
1337       // soundcard name like "HDA ATI SB"
1338       setWindowTitle(i18n("KDE Mixer") + " - " + kmw->mixer()->readableName());
1339       if (!m_dontSetDefaultCardOnStart)
1340         m_defaultCardOnStart = kmw->getGuiprof()->getId();
1341       // As switching the tab does NOT mean switching the master card, we do not need to update dock icon here.
1342       // It would lead to unnecesary flickering of the (complete) dock area.
1343
1344       // We only show the "Configure Channels..." menu item if the mixer is not dynamic
1345       ViewBase* view = kmw->currentView();
1346       QAction* action = actionCollection()->action(
1347           "toggle_channels_currentview");
1348       if (view && action)
1349         action->setVisible(!view->isDynamic());
1350     }
1351 }
1352
1353 #include "moc_kmix.cpp"