mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-24 02:42:52 +00:00
1353 lines
44 KiB
C++
1353 lines
44 KiB
C++
/*
|
|
* KMix -- KDE's full featured mini mixer
|
|
*
|
|
* Copyright 1996-2014 The KMix authors. Maintainer: Christian Esken <esken@kde.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this program; if not, write to the Free
|
|
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "apps/kmix.h"
|
|
|
|
// include files for QT
|
|
#include <QCheckBox>
|
|
#include <QLabel>
|
|
#include <QDesktopWidget>
|
|
#include <QPushButton>
|
|
#include <qradiobutton.h>
|
|
#include <QCursor>
|
|
#include <QString>
|
|
#include <QMenuBar>
|
|
#include <QProcess>
|
|
|
|
// include files for KDE
|
|
#include <KConfigSkeleton>
|
|
#include <kcombobox.h>
|
|
#include <kiconloader.h>
|
|
#include <kmessagebox.h>
|
|
#include <klocale.h>
|
|
#include <kconfig.h>
|
|
#include <kaction.h>
|
|
#include <kapplication.h>
|
|
#include <kstandardaction.h>
|
|
#include <kmenu.h>
|
|
#include <khelpmenu.h>
|
|
#include <kdebug.h>
|
|
#include <kxmlguifactory.h>
|
|
#include <kglobal.h>
|
|
#include <kactioncollection.h>
|
|
#include <ktoggleaction.h>
|
|
#include <ksystemeventfilter.h>
|
|
#include <KTabWidget>
|
|
|
|
// KMix
|
|
#include "gui/guiprofile.h"
|
|
#include "core/ControlManager.h"
|
|
#include "core/GlobalConfig.h"
|
|
#include "core/MasterControl.h"
|
|
#include "core/MediaController.h"
|
|
#include "core/mixertoolbox.h"
|
|
#include "core/kmixdevicemanager.h"
|
|
#include "gui/kmixerwidget.h"
|
|
#include "gui/kmixprefdlg.h"
|
|
#include "gui/kmixdockwidget.h"
|
|
#include "gui/kmixtoolbox.h"
|
|
#include "core/version.h"
|
|
#include "gui/viewdockareapopup.h"
|
|
#include "gui/dialogaddview.h"
|
|
#include "gui/dialogselectmaster.h"
|
|
#include "dbus/dbusmixsetwrapper.h"
|
|
#include "gui/osdwidget.h"
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/XF86keysym.h>
|
|
#include <fixx11h.h>
|
|
|
|
/* KMixWindow
|
|
* Constructs a mixer window (KMix main window)
|
|
*/
|
|
|
|
static KeyCode s_xf86lowervolume = 0;
|
|
static KeyCode s_xf86raisevolume = 0;
|
|
|
|
KMixWindow::KMixWindow(bool invisible) :
|
|
KXmlGuiWindow(0, Qt::WindowContextHelpButtonHint),
|
|
m_multiDriverMode(false), // -<- I never-ever want the multi-drivermode to be activated by accident
|
|
m_dockWidget(), m_dsm(0), m_dontSetDefaultCardOnStart(false)
|
|
{
|
|
setObjectName(QLatin1String("KMixWindow"));
|
|
// disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in
|
|
setAttribute(Qt::WA_DeleteOnClose, false);
|
|
|
|
initActions(); // init actions first, so we can use them in the loadConfig() already
|
|
loadConfig(); // Load config before initMixer(), e.g. due to "MultiDriver" keyword
|
|
initActionsLate(); // init actions that require a loaded config
|
|
KGlobal::locale()->insertCatalog(QLatin1String("kmix-controls"));
|
|
initWidgets();
|
|
initPrefDlg();
|
|
DBusMixSetWrapper::initialize(this, "/Mixers");
|
|
MixerToolBox::instance()->initMixer(m_multiDriverMode, m_backendFilter,
|
|
m_hwInfoString);
|
|
KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance();
|
|
initActionsAfterInitMixer(); // init actions that require initialized mixer backend(s).
|
|
|
|
recreateGUI(false);
|
|
if (m_wsMixers->count() < 1)
|
|
{
|
|
// Something is wrong. Perhaps a hardware or driver or backend change. Let KMix search harder
|
|
recreateGUI(false, QString(), true);
|
|
}
|
|
|
|
if (!kapp->isSessionRestored() ) // done by the session manager otherwise
|
|
setInitialSize();
|
|
|
|
theKMixDeviceManager->initHotplug();
|
|
connect(theKMixDeviceManager, SIGNAL(plugged(const char*,QString,QString&)),
|
|
SLOT (plugged(const char*,QString,QString&)));
|
|
connect(theKMixDeviceManager, SIGNAL(unplugged(QString)),
|
|
SLOT (unplugged(QString)));
|
|
if (m_startVisible && !invisible)
|
|
show(); // Started visible
|
|
|
|
connect(kapp, SIGNAL(aboutToQuit()), SLOT(saveConfig()) );
|
|
|
|
ControlManager::instance().addListener(
|
|
QString(), // All mixers (as the Global master Mixer might change)
|
|
(ControlChangeType::Type)(ControlChangeType::ControlList | ControlChangeType::MasterChanged),
|
|
this,
|
|
QString("KMixWindow")
|
|
);
|
|
|
|
// Send an initial volume refresh (otherwise all volumes are 0 until the next change)
|
|
ControlManager::instance().announce(QString(), ControlChangeType::Volume, QString("Startup"));
|
|
}
|
|
|
|
KMixWindow::~KMixWindow()
|
|
{
|
|
if (m_autouseMultimediaKeys) {
|
|
XUngrabKey(
|
|
QX11Info::display(),
|
|
s_xf86lowervolume,
|
|
AnyModifier, QX11Info::appRootWindow()
|
|
);
|
|
|
|
XUngrabKey(
|
|
QX11Info::display(),
|
|
s_xf86raisevolume,
|
|
AnyModifier, QX11Info::appRootWindow()
|
|
);
|
|
}
|
|
|
|
ControlManager::instance().removeListener(this);
|
|
|
|
delete m_dsm;
|
|
delete osdWidget;
|
|
|
|
// -1- Cleanup Memory: clearMixerWidgets
|
|
while (m_wsMixers->count() != 0)
|
|
{
|
|
QWidget *mw = m_wsMixers->widget(0);
|
|
m_wsMixers->removeTab(0);
|
|
delete mw;
|
|
}
|
|
|
|
// -2- Mixer HW
|
|
MixerToolBox::instance()->deinitMixer();
|
|
|
|
// -3- Action collection (just to please Valgrind)
|
|
actionCollection()->clear();
|
|
|
|
// GUIProfile cache should be cleared very very late, as GUIProfile instances are used in the Views, which
|
|
// means main window and potentially also in the tray popup (at least we might do so in the future).
|
|
// This place here could be to early, if we would start to GUIProfile outside KMixWIndow, e.g. in the tray popup.
|
|
// Until we do so, this is the best place to call clearCache(). Later, e.g. in main() would likely be problematic.
|
|
|
|
GUIProfile::clearCache();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMixWindow::controlsChange(int changeType)
|
|
{
|
|
ControlChangeType::Type type = ControlChangeType::fromInt(changeType);
|
|
switch (type )
|
|
{
|
|
case ControlChangeType::ControlList:
|
|
case ControlChangeType::MasterChanged:
|
|
updateDocking();
|
|
break;
|
|
|
|
default:
|
|
ControlManager::warnUnexpectedChangeType(type, this);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
KMixWindow::initActions()
|
|
{
|
|
// file menu
|
|
KStandardAction::quit(this, SLOT(quit()), actionCollection());
|
|
|
|
// settings menu
|
|
_actionShowMenubar = KStandardAction::showMenubar(this, SLOT(toggleMenuBar()),
|
|
actionCollection());
|
|
//actionCollection()->addAction( a->objectName(), a );
|
|
KStandardAction::preferences(this, SLOT(showSettings()), actionCollection());
|
|
KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()),
|
|
actionCollection());
|
|
KAction* action = actionCollection()->addAction("launch_kdesoundsetup");
|
|
action->setText(i18n("Audio Setup"));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(slotKdeAudioSetupExec()));
|
|
|
|
action = actionCollection()->addAction("hwinfo");
|
|
action->setText(i18n("Hardware &Information"));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(slotHWInfo()));
|
|
action = actionCollection()->addAction("hide_kmixwindow");
|
|
action->setText(i18n("Hide Mixer Window"));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(hideOrClose()));
|
|
action->setShortcut(QKeySequence(Qt::Key_Escape));
|
|
action = actionCollection()->addAction("toggle_channels_currentview");
|
|
action->setText(i18n("Configure &Channels..."));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(slotConfigureCurrentView()));
|
|
action = actionCollection()->addAction("select_master");
|
|
action->setText(i18n("Select Master Channel..."));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(slotSelectMaster()));
|
|
|
|
action = actionCollection()->addAction("save_1");
|
|
action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_1));
|
|
action->setText(i18n("Save volume profile 1"));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes1()));
|
|
|
|
action = actionCollection()->addAction("save_2");
|
|
action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_2));
|
|
action->setText(i18n("Save volume profile 2"));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes2()));
|
|
|
|
action = actionCollection()->addAction("save_3");
|
|
action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_3));
|
|
action->setText(i18n("Save volume profile 3"));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes3()));
|
|
|
|
action = actionCollection()->addAction("save_4");
|
|
action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_4));
|
|
action->setText(i18n("Save volume profile 4"));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes4()));
|
|
|
|
action = actionCollection()->addAction("load_1");
|
|
action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_1));
|
|
action->setText(i18n("Load volume profile 1"));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes1()));
|
|
|
|
action = actionCollection()->addAction("load_2");
|
|
action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_2));
|
|
action->setText(i18n("Load volume profile 2"));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes2()));
|
|
|
|
action = actionCollection()->addAction("load_3");
|
|
action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_3));
|
|
action->setText(i18n("Load volume profile 3"));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes3()));
|
|
|
|
action = actionCollection()->addAction("load_4");
|
|
action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_4));
|
|
action->setText(i18n("Load volume profile 4"));
|
|
connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes4()));
|
|
|
|
osdWidget = new OSDWidget();
|
|
|
|
createGUI(QLatin1String("kmixui.rc"));
|
|
}
|
|
|
|
void
|
|
KMixWindow::initActionsLate()
|
|
{
|
|
if (m_autouseMultimediaKeys)
|
|
{
|
|
KAction* globalAction = actionCollection()->addAction("increase_volume");
|
|
globalAction->setText(i18n("Increase Volume"));
|
|
connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotIncreaseVolume()));
|
|
|
|
globalAction = actionCollection()->addAction("decrease_volume");
|
|
globalAction->setText(i18n("Decrease Volume"));
|
|
connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotDecreaseVolume()));
|
|
|
|
globalAction = actionCollection()->addAction("mute");
|
|
globalAction->setText(i18n("Mute"));
|
|
connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotMute()));
|
|
|
|
s_xf86lowervolume = XKeysymToKeycode(QX11Info::display(), XF86XK_AudioLowerVolume);
|
|
s_xf86raisevolume = XKeysymToKeycode(QX11Info::display(), XF86XK_AudioRaiseVolume);
|
|
|
|
XGrabKey(
|
|
QX11Info::display(),
|
|
s_xf86lowervolume,
|
|
AnyModifier, QX11Info::appRootWindow(), False, GrabModeAsync, GrabModeAsync
|
|
);
|
|
|
|
XGrabKey(
|
|
QX11Info::display(),
|
|
s_xf86raisevolume,
|
|
AnyModifier, QX11Info::appRootWindow(), False, GrabModeAsync, GrabModeAsync
|
|
);
|
|
|
|
KSystemEventFilter::installEventFilter(this);
|
|
}
|
|
}
|
|
|
|
bool KMixWindow::x11Event(XEvent *xevent)
|
|
{
|
|
if (xevent->type == KeyPress) {
|
|
// qDebug() << Q_FUNC_INFO << xevent << xevent->xkey.keycode;
|
|
// qDebug() << Q_FUNC_INFO << XF86XK_AudioLowerVolume << s_xf86lowervolume;
|
|
// qDebug() << Q_FUNC_INFO << XF86XK_AudioRaiseVolume << s_xf86raisevolume;
|
|
if (xevent->xkey.keycode == s_xf86lowervolume) {
|
|
increaseOrDecreaseVolume(false);
|
|
return true;
|
|
} else if (xevent->xkey.keycode == s_xf86raisevolume) {
|
|
increaseOrDecreaseVolume(true);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
KMixWindow::initActionsAfterInitMixer()
|
|
{
|
|
QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new", KIconLoader::Toolbar, KIconLoader::SizeSmall);
|
|
QPushButton* _cornerLabelNew = new QPushButton();
|
|
_cornerLabelNew->setIcon(cornerNewPM);
|
|
//cornerLabelNew->setSizePolicy(QSizePolicy());
|
|
m_wsMixers->setCornerWidget(_cornerLabelNew, Qt::TopLeftCorner);
|
|
connect(_cornerLabelNew, SIGNAL(clicked()), SLOT (newView()));
|
|
}
|
|
|
|
void
|
|
KMixWindow::initPrefDlg()
|
|
{
|
|
KMixPrefDlg* prefDlg = KMixPrefDlg::createInstance(this, GlobalConfig::instance());
|
|
connect(prefDlg, SIGNAL(kmixConfigHasChanged()), SLOT(applyPrefs()));
|
|
}
|
|
|
|
void
|
|
KMixWindow::initWidgets()
|
|
{
|
|
m_wsMixers = new KTabWidget();
|
|
m_wsMixers->setDocumentMode(true);
|
|
setCentralWidget(m_wsMixers);
|
|
m_wsMixers->setTabsClosable(false);
|
|
connect(m_wsMixers, SIGNAL(tabCloseRequested(int)),
|
|
SLOT(saveAndCloseView(int)));
|
|
|
|
QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new",
|
|
KIconLoader::Toolbar, KIconLoader::SizeSmall);
|
|
|
|
connect(m_wsMixers, SIGNAL(currentChanged(int)), SLOT(newMixerShown(int)));
|
|
|
|
// show menubar if the actions says so (or if the action does not exist)
|
|
menuBar()->setVisible(
|
|
(_actionShowMenubar == 0) || _actionShowMenubar->isChecked());
|
|
}
|
|
|
|
void
|
|
KMixWindow::setInitialSize()
|
|
{
|
|
KConfigGroup config(KGlobal::config(), "Global");
|
|
|
|
// HACK: QTabWidget will bound its sizeHint to 200x200 unless scrollbuttons
|
|
// are disabled, so we disable them, get a decent sizehint and enable them
|
|
// back
|
|
m_wsMixers->setUsesScrollButtons(false);
|
|
QSize defSize = sizeHint();
|
|
m_wsMixers->setUsesScrollButtons(true);
|
|
QSize size = config.readEntry("Size", defSize);
|
|
if (!size.isEmpty())
|
|
resize(size);
|
|
|
|
QPoint defPos = pos();
|
|
QPoint pos = config.readEntry("Position", defPos);
|
|
move(pos);
|
|
}
|
|
|
|
|
|
void KMixWindow::removeDock()
|
|
{
|
|
if (m_dockWidget)
|
|
{
|
|
m_dockWidget->deleteLater();
|
|
m_dockWidget = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates or deletes the KMixDockWidget, depending on whether there is a Mixer instance available.
|
|
*
|
|
* @returns true, if the docking succeeded. Failure usually means that there
|
|
* was no suitable mixer control selected.
|
|
*/
|
|
bool KMixWindow::updateDocking()
|
|
{
|
|
GlobalConfigData& gcd = GlobalConfig::instance().data;
|
|
|
|
if ( !gcd.showDockWidget || Mixer::mixers().isEmpty())
|
|
{
|
|
removeDock();
|
|
return false;
|
|
}
|
|
if (!m_dockWidget)
|
|
{
|
|
m_dockWidget = new KMixDockWidget(this);
|
|
}
|
|
return true;
|
|
}
|
|
void
|
|
KMixWindow::saveConfig()
|
|
{
|
|
saveBaseConfig();
|
|
saveViewConfig();
|
|
saveVolumes();
|
|
#warning We must Sync here, or we will lose configuration data. The reson for that is unknown.
|
|
|
|
// TODO cesken The reason for not writing might be that we have multiple cascaded KConfig objects. I must migrate to KSharedConfig !!!
|
|
KGlobal::config()->sync();
|
|
kDebug()
|
|
<< "Saved config ... sync finished";
|
|
}
|
|
|
|
void KMixWindow::saveBaseConfig()
|
|
{
|
|
GlobalConfig::instance().writeConfig();
|
|
|
|
KConfigGroup config(KGlobal::config(), "Global");
|
|
|
|
config.writeEntry("Size", size());
|
|
config.writeEntry("Position", pos());
|
|
// Cannot use isVisible() here, as in the "aboutToQuit()" case this widget is already hidden.
|
|
// (Please note that the problem was only there when quitting via Systray - esken).
|
|
// Using it again, as internal behaviour has changed with KDE4
|
|
config.writeEntry("Visible", isVisible());
|
|
config.writeEntry("Menubar", _actionShowMenubar->isChecked());
|
|
config.writeEntry("Soundmenu.Mixers", GlobalConfig::instance().getMixersForSoundmenu().toList());
|
|
|
|
config.writeEntry("DefaultCardOnStart", m_defaultCardOnStart);
|
|
config.writeEntry("ConfigVersion", KMIX_CONFIG_VERSION);
|
|
config.writeEntry("AutoUseMultimediaKeys", m_autouseMultimediaKeys);
|
|
|
|
MasterControl& master = Mixer::getGlobalMasterPreferred();
|
|
if (master.isValid())
|
|
{
|
|
config.writeEntry("MasterMixer", master.getCard());
|
|
config.writeEntry("MasterMixerDevice", master.getControl());
|
|
}
|
|
QString mixerIgnoreExpression = MixerToolBox::instance()->mixerIgnoreExpression();
|
|
config.writeEntry("MixerIgnoreExpression", mixerIgnoreExpression);
|
|
|
|
kDebug()
|
|
<< "Base configuration saved";
|
|
}
|
|
|
|
void KMixWindow::saveViewConfig()
|
|
{
|
|
QMap<QString, QStringList> mixerViews;
|
|
|
|
// The following loop is necessary for the case that the user has hidden all views for a Mixer instance.
|
|
// Otherwise we would not save the Meta information (step -2- below for that mixer.
|
|
// We also do not save dynamic mixers (e.g. PulseAudio)
|
|
foreach ( Mixer* mixer, Mixer::mixers() )
|
|
{
|
|
if ( !mixer->isDynamic() )
|
|
{
|
|
mixerViews[mixer->id()]; // just insert a map entry
|
|
}
|
|
}
|
|
|
|
// -1- Save the views themselves
|
|
for (int i = 0; i < m_wsMixers->count(); ++i)
|
|
{
|
|
QWidget *w = m_wsMixers->widget(i);
|
|
if (w->inherits("KMixerWidget"))
|
|
{
|
|
KMixerWidget* mw = (KMixerWidget*) w;
|
|
// Here also Views are saved. even for Mixers that are closed. This is necessary when unplugging cards.
|
|
// Otherwise the user will be confused afer re-plugging the card (as the config was not saved).
|
|
mw->saveConfig(KGlobal::config().data());
|
|
// add the view to the corresponding mixer list, so we can save a views-per-mixer list below
|
|
if (!mw->mixer()->isDynamic())
|
|
{
|
|
QStringList& qsl = mixerViews[mw->mixer()->id()];
|
|
qsl.append(mw->getGuiprof()->getId());
|
|
}
|
|
}
|
|
}
|
|
|
|
// -2- Save Meta-Information (which views, and in which order). views-per-mixer list
|
|
KConfigGroup pconfig(KGlobal::config(), "Profiles");
|
|
QMap<QString, QStringList>::const_iterator itEnd = mixerViews.constEnd();
|
|
for (QMap<QString, QStringList>::const_iterator it = mixerViews.constBegin(); it != itEnd; ++it)
|
|
{
|
|
const QString& mixerProfileKey = it.key(); // this is actually some mixer->id()
|
|
const QStringList& qslProfiles = it.value();
|
|
pconfig.writeEntry(mixerProfileKey, qslProfiles);
|
|
kDebug()
|
|
<< "Save Profile List for " << mixerProfileKey << ", number of views is " << qslProfiles.count();
|
|
}
|
|
|
|
kDebug()
|
|
<< "View configuration saved";
|
|
}
|
|
|
|
/**
|
|
* Stores the volumes of all mixers Can be restored via loadVolumes() or
|
|
* the kmixctrl application.
|
|
*/
|
|
void
|
|
KMixWindow::saveVolumes()
|
|
{
|
|
saveVolumes(QString());
|
|
}
|
|
|
|
void
|
|
KMixWindow::saveVolumes(QString postfix)
|
|
{
|
|
const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix);
|
|
KConfig *cfg = new KConfig(kmixctrlRcFilename);
|
|
for (int i = 0; i < Mixer::mixers().count(); ++i)
|
|
{
|
|
Mixer *mixer = (Mixer::mixers())[i];
|
|
if (mixer->isOpen())
|
|
{ // protect from unplugged devices (better do *not* save them)
|
|
mixer->volumeSave(cfg);
|
|
}
|
|
}
|
|
cfg->sync();
|
|
delete cfg;
|
|
kDebug()
|
|
<< "Volume configuration saved";
|
|
}
|
|
|
|
QString
|
|
KMixWindow::getKmixctrlRcFilename(QString postfix)
|
|
{
|
|
QString kmixctrlRcFilename("kmixctrlrc");
|
|
if (!postfix.isEmpty())
|
|
{
|
|
kmixctrlRcFilename.append(".").append(postfix);
|
|
}
|
|
return kmixctrlRcFilename;
|
|
}
|
|
|
|
void
|
|
KMixWindow::loadConfig()
|
|
{
|
|
loadBaseConfig();
|
|
|
|
//loadViewConfig(); // mw->loadConfig() explicitly called always after creating mw.
|
|
//loadVolumes(); // not in use
|
|
|
|
// create an initial snapshot, so we have a reference of the state before changes through the preferences dialog
|
|
configDataSnapshot = GlobalConfig::instance().data;
|
|
}
|
|
|
|
void
|
|
KMixWindow::loadBaseConfig()
|
|
{
|
|
KConfigGroup config(KGlobal::config(), "Global");
|
|
|
|
QList<QString> preferredMixersInSoundMenu;
|
|
preferredMixersInSoundMenu = config.readEntry("Soundmenu.Mixers", preferredMixersInSoundMenu);
|
|
GlobalConfig::instance().setMixersForSoundmenu(preferredMixersInSoundMenu.toSet());
|
|
|
|
m_startVisible = config.readEntry("Visible", false);
|
|
m_multiDriverMode = config.readEntry("MultiDriver", false);
|
|
m_defaultCardOnStart = config.readEntry("DefaultCardOnStart", "");
|
|
m_autouseMultimediaKeys = config.readEntry("AutoUseMultimediaKeys", true);
|
|
QString mixerMasterCard = config.readEntry("MasterMixer", "");
|
|
QString masterDev = config.readEntry("MasterMixerDevice", "");
|
|
Mixer::setGlobalMaster(mixerMasterCard, masterDev, true);
|
|
QString mixerIgnoreExpression = config.readEntry("MixerIgnoreExpression",
|
|
"Modem");
|
|
MixerToolBox::instance()->setMixerIgnoreExpression(mixerIgnoreExpression);
|
|
|
|
// --- Advanced options, without GUI: START -------------------------------------
|
|
QString volumePercentageStepString = config.readEntry("VolumePercentageStep");
|
|
if (!volumePercentageStepString.isNull())
|
|
{
|
|
float volumePercentageStep = volumePercentageStepString.toFloat();
|
|
if (volumePercentageStep > 0 && volumePercentageStep <= 100)
|
|
Volume::VOLUME_STEP_DIVISOR = (100 / volumePercentageStep);
|
|
}
|
|
|
|
// --- Advanced options, without GUI: END -------------------------------------
|
|
|
|
m_backendFilter = config.readEntry<>("Backends", QList<QString>());
|
|
kDebug() << "Backends: " << m_backendFilter;
|
|
|
|
// show/hide menu bar
|
|
bool showMenubar = config.readEntry("Menubar", true);
|
|
|
|
if (_actionShowMenubar)
|
|
_actionShowMenubar->setChecked(showMenubar);
|
|
}
|
|
|
|
/**
|
|
* Loads the volumes of all mixers from kmixctrlrc.
|
|
* In other words:
|
|
* Restores the default voumes as stored via saveVolumes() or the
|
|
* execution of "kmixctrl --save"
|
|
*/
|
|
|
|
void
|
|
KMixWindow::loadVolumes()
|
|
{
|
|
loadVolumes(QString());
|
|
}
|
|
|
|
void
|
|
KMixWindow::loadVolumes(QString postfix)
|
|
{
|
|
kDebug()
|
|
<< "About to load config (Volume)";
|
|
const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix);
|
|
|
|
KConfig *cfg = new KConfig(kmixctrlRcFilename);
|
|
for (int i = 0; i < Mixer::mixers().count(); ++i)
|
|
{
|
|
Mixer *mixer = (Mixer::mixers())[i];
|
|
mixer->volumeLoad(cfg);
|
|
}
|
|
delete cfg;
|
|
}
|
|
|
|
|
|
void
|
|
KMixWindow::recreateGUIwithSavingView()
|
|
{
|
|
// saveViewConfig();
|
|
recreateGUI(true);
|
|
}
|
|
|
|
void
|
|
KMixWindow::recreateGUI(bool saveConfig)
|
|
{
|
|
recreateGUI(saveConfig, QString(), false);
|
|
}
|
|
|
|
/**
|
|
* Create or recreate the Mixer GUI elements
|
|
*
|
|
* @param saveConfig Whether to save all View configurations before recreating
|
|
* @param forceNewTab To enforce opening a new tab, even when the profileList in the kmixrc is empty.
|
|
* It should only be set to "true" in case of a Hotplug (because then the user definitely expects a new Tab to show).
|
|
*/
|
|
void
|
|
KMixWindow::recreateGUI(bool saveConfig, const QString& mixerId,
|
|
bool forceNewTab)
|
|
{
|
|
// -1- Remember which of the tabs is currently selected for restoration for re-insertion
|
|
int oldTabPosition = m_wsMixers->currentIndex();
|
|
|
|
if (saveConfig)
|
|
saveViewConfig(); // save the state before recreating
|
|
|
|
// -2- RECREATE THE ALREADY EXISTING TABS **********************************
|
|
QMap<Mixer*, bool> mixerHasProfile;
|
|
|
|
// -2a- Build a list of all active profiles in the main window (that means: from all tabs)
|
|
QList<GUIProfile*> activeGuiProfiles;
|
|
for (int i = 0; i < m_wsMixers->count(); ++i)
|
|
{
|
|
KMixerWidget* kmw = qobject_cast<KMixerWidget*>(m_wsMixers->widget(i));
|
|
if (kmw)
|
|
{
|
|
activeGuiProfiles.append(kmw->getGuiprof());
|
|
}
|
|
}
|
|
|
|
// TODO The following loop is a bit buggy, as it iterates over all cached Profiles. But that is wrong for Tabs that got closed.
|
|
// I need to loop over something else, e.g. a profile list built from the currently open Tabs.
|
|
// Or (if it that is easier) I might discard the Profile from the cache on "close-tab" (but that must also include unplug actions).
|
|
foreach( GUIProfile* guiprof, activeGuiProfiles){
|
|
KMixerWidget* kmw = findKMWforTab(guiprof->getId());
|
|
Mixer *mixer = Mixer::findMixer( guiprof->getMixerId() );
|
|
if ( mixer == 0 )
|
|
{
|
|
kError() << "MixerToolBox::find() hasn't found the Mixer for the profile " << guiprof->getId();
|
|
continue;
|
|
}
|
|
mixerHasProfile[mixer] = true;
|
|
if ( kmw == 0 )
|
|
{
|
|
// does not yet exist => create
|
|
addMixerWidget(mixer->id(), guiprof->getId(), -1);
|
|
}
|
|
else
|
|
{
|
|
// did exist => remove and insert new guiprof at old position
|
|
int indexOfTab = m_wsMixers->indexOf(kmw);
|
|
if ( indexOfTab != -1 ) m_wsMixers->removeTab(indexOfTab);
|
|
delete kmw;
|
|
addMixerWidget(mixer->id(), guiprof->getId(), indexOfTab);
|
|
}
|
|
} // Loop over all GUIProfile's
|
|
|
|
// -3- ADD TABS FOR Mixer instances that have no tab yet **********************************
|
|
KConfigGroup pconfig(KGlobal::config(), "Profiles");
|
|
foreach ( Mixer *mixer, Mixer::mixers()){
|
|
if ( mixerHasProfile.contains(mixer))
|
|
{
|
|
continue; // OK, this mixer already has a profile => skip it
|
|
}
|
|
// No TAB YET => This should mean KMix is just started, or the user has just plugged in a card
|
|
bool profileListHasKey = false;
|
|
QStringList profileList;
|
|
bool aProfileWasAddedSucesufully = false;
|
|
|
|
if ( !mixer->isDynamic() )
|
|
{
|
|
// We do not support save profiles for dynamic mixers (i.e. PulseAudio)
|
|
|
|
profileListHasKey = pconfig.hasKey( mixer->id() );// <<< SHOULD be before the following line
|
|
profileList = pconfig.readEntry( mixer->id(), QStringList() );
|
|
|
|
foreach ( QString profileId, profileList)
|
|
{
|
|
// This handles the profileList form the kmixrc
|
|
kDebug() << "Now searching for profile: " << profileId;
|
|
GUIProfile* guiprof = GUIProfile::find(mixer, profileId, true, false);// ### Card specific profile ###
|
|
if ( guiprof != 0 )
|
|
{
|
|
addMixerWidget(mixer->id(), guiprof->getId(), -1);
|
|
aProfileWasAddedSucesufully = true;
|
|
}
|
|
else
|
|
{
|
|
kError() << "Cannot load profile " << profileId << " . It was removed by the user, or the KMix config file is defective.";
|
|
}
|
|
}
|
|
}
|
|
|
|
// The we_need_a_fallback case is a bit tricky. Please ask the author (cesken) before even considering to change the code.
|
|
bool we_need_a_fallback = !aProfileWasAddedSucesufully;// we *possibly* want a fallback, if we couldn't add one
|
|
bool thisMixerShouldBeForced = forceNewTab && ( mixerId.isEmpty() || (mixer->id() == mixerId) );
|
|
we_need_a_fallback = we_need_a_fallback && ( thisMixerShouldBeForced || !profileListHasKey );// Additional requirement: "forced-tab-for-this-mixer" OR "no key stored in kmixrc yet"
|
|
if ( we_need_a_fallback )
|
|
{
|
|
// The profileList was empty or nothing could be loaded
|
|
// (Hint: This means the user cannot hide a device completely
|
|
|
|
// Lets try a bunch of fallback strategies:
|
|
GUIProfile* guiprof = 0;
|
|
if ( !mixer->isDynamic() )
|
|
{
|
|
// We know that GUIProfile::find() will return 0 if the mixer is dynamic, so don't bother checking.
|
|
kDebug() << "Attempting to find a card-specific GUI Profile for the mixer " << mixer->id();
|
|
guiprof = GUIProfile::find(mixer, QString("default"), false, false);// ### Card specific profile ###
|
|
if ( guiprof == 0 )
|
|
{
|
|
kDebug() << "Not found. Attempting to find a generic GUI Profile for the mixer " << mixer->id();
|
|
guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ###
|
|
}
|
|
}
|
|
if ( guiprof == 0)
|
|
{
|
|
kDebug() << "Using fallback GUI Profile for the mixer " << mixer->id();
|
|
// This means there is neither card specific nor card unspecific profile
|
|
// This is the case for some backends (as they don't ship profiles).
|
|
guiprof = GUIProfile::fallbackProfile(mixer);
|
|
}
|
|
|
|
if ( guiprof != 0 )
|
|
{
|
|
guiprof->setDirty(); // All fallback => dirty
|
|
addMixerWidget(mixer->id(), guiprof->getId(), -1);
|
|
}
|
|
else
|
|
{
|
|
kError() << "Cannot use ANY profile (including Fallback) for mixer " << mixer->id() << " . This is impossible, and thus this mixer can NOT be used.";
|
|
}
|
|
|
|
}
|
|
}
|
|
mixerHasProfile.clear();
|
|
|
|
// -4- FINALIZE **********************************
|
|
if (m_wsMixers->count() > 0)
|
|
{
|
|
if (oldTabPosition >= 0)
|
|
{
|
|
m_wsMixers->setCurrentIndex(oldTabPosition);
|
|
}
|
|
bool dockingSucceded = updateDocking();
|
|
if (!dockingSucceded && !Mixer::mixers().empty())
|
|
{
|
|
show(); // avoid invisible and unaccessible main window
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No soundcard found. Do not complain, but sit in the background, and wait for newly plugged soundcards.
|
|
updateDocking(); // -<- removes the DockIcon
|
|
hide();
|
|
}
|
|
|
|
}
|
|
|
|
KMixerWidget*
|
|
KMixWindow::findKMWforTab(const QString& kmwId)
|
|
{
|
|
for (int i = 0; i < m_wsMixers->count(); ++i)
|
|
{
|
|
KMixerWidget* kmw = (KMixerWidget*) m_wsMixers->widget(i);
|
|
if (kmw->getGuiprof()->getId() == kmwId)
|
|
{
|
|
return kmw;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
KMixWindow::newView()
|
|
{
|
|
if (Mixer::mixers().empty())
|
|
{
|
|
kError() << "Trying to create a View, but no Mixer exists";
|
|
return; // should never happen
|
|
}
|
|
|
|
Mixer *mixer = Mixer::mixers()[0];
|
|
QPointer<DialogAddView> dav = new DialogAddView(this, mixer);
|
|
int ret = dav->exec();
|
|
|
|
if (QDialog::Accepted == ret)
|
|
{
|
|
QString profileName = dav->getresultViewName();
|
|
QString mixerId = dav->getresultMixerId();
|
|
mixer = Mixer::findMixer(mixerId);
|
|
kDebug()
|
|
<< ">>> mixer = " << mixerId << " -> " << mixer;
|
|
|
|
GUIProfile* guiprof = GUIProfile::find(mixer, profileName, false, false);
|
|
if (guiprof == 0)
|
|
{
|
|
guiprof = GUIProfile::find(mixer, profileName, false, true);
|
|
}
|
|
|
|
if (guiprof == 0)
|
|
{
|
|
static const QString msg(
|
|
i18n("Cannot add view - GUIProfile is invalid."));
|
|
errorPopup(msg);
|
|
}
|
|
else
|
|
{
|
|
bool ret = addMixerWidget(mixer->id(), guiprof->getId(), -1);
|
|
if (ret == false)
|
|
{
|
|
errorPopup(i18n("View already exists. Cannot add View."));
|
|
}
|
|
}
|
|
|
|
delete dav;
|
|
}
|
|
|
|
//kDebug() << "Exit";
|
|
}
|
|
|
|
/**
|
|
* Save the view and close it
|
|
*
|
|
* @arg idx The index in the TabWidget
|
|
*/
|
|
void
|
|
KMixWindow::saveAndCloseView(int idx)
|
|
{
|
|
kDebug()
|
|
<< "Enter";
|
|
QWidget *w = m_wsMixers->widget(idx);
|
|
KMixerWidget* kmw = ::qobject_cast<KMixerWidget*>(w);
|
|
if (kmw)
|
|
{
|
|
kmw->saveConfig(KGlobal::config().data()); // -<- This alone is not enough, as I need to save the META information as well. Thus use saveViewConfig() below
|
|
m_wsMixers->removeTab(idx);
|
|
updateTabsClosable();
|
|
saveViewConfig();
|
|
delete kmw;
|
|
}
|
|
kDebug()
|
|
<< "Exit";
|
|
}
|
|
|
|
void
|
|
KMixWindow::plugged(const char* driverName, const QString& udi, QString& dev)
|
|
{
|
|
kDebug()
|
|
<< "Plugged: dev=" << dev << "(" << driverName << ") udi=" << udi << "\n";
|
|
QString driverNameString;
|
|
driverNameString = driverName;
|
|
int devNum = dev.toInt();
|
|
Mixer *mixer = new Mixer(driverNameString, devNum);
|
|
if (mixer != 0)
|
|
{
|
|
kDebug()
|
|
<< "Plugged: dev=" << dev << "\n";
|
|
MixerToolBox::instance()->possiblyAddMixer(mixer);
|
|
recreateGUI(true, mixer->id(), true);
|
|
}
|
|
}
|
|
|
|
void
|
|
KMixWindow::unplugged(const QString& udi)
|
|
{
|
|
kDebug()
|
|
<< "Unplugged: udi=" << udi << "\n";
|
|
for (int i = 0; i < Mixer::mixers().count(); ++i)
|
|
{
|
|
Mixer *mixer = (Mixer::mixers())[i];
|
|
// kDebug(67100) << "Try Match with:" << mixer->udi() << "\n";
|
|
if (mixer->udi() == udi)
|
|
{
|
|
kDebug() << "Unplugged Match: Removing udi=" << udi << "\n";
|
|
//KMixToolBox::notification("MasterFallback", "aaa");
|
|
bool globalMasterMixerDestroyed = (mixer == Mixer::getGlobalMasterMixer());
|
|
// Part 1) Remove Tab
|
|
for (int i = 0; i < m_wsMixers->count(); ++i)
|
|
{
|
|
QWidget *w = m_wsMixers->widget(i);
|
|
KMixerWidget* kmw = ::qobject_cast<KMixerWidget*>(w);
|
|
if (kmw && kmw->mixer() == mixer)
|
|
{
|
|
saveAndCloseView(i);
|
|
i = -1; // Restart loop from scratch (indices are most likely invalidated at removeTab() )
|
|
}
|
|
}
|
|
MixerToolBox::instance()->removeMixer(mixer);
|
|
// Check whether the Global Master disappeared, and select a new one if necessary
|
|
std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
|
|
if (globalMasterMixerDestroyed || md.get() == 0)
|
|
{
|
|
// We don't know what the global master should be now.
|
|
// So lets play stupid, and just select the recommended master of the first device
|
|
if (Mixer::mixers().count() > 0)
|
|
{
|
|
std::shared_ptr<MixDevice> master =
|
|
((Mixer::mixers())[0])->getLocalMasterMD();
|
|
if (master.get() != 0)
|
|
{
|
|
QString localMaster = master->id();
|
|
Mixer::setGlobalMaster(((Mixer::mixers())[0])->id(), localMaster, false);
|
|
|
|
QString text;
|
|
text =
|
|
i18n(
|
|
"The soundcard containing the master device was unplugged. Changing to control %1 on card %2.",
|
|
master->readableName(),
|
|
((Mixer::mixers())[0])->readableName());
|
|
KMixToolBox::notification("MasterFallback", text);
|
|
}
|
|
}
|
|
}
|
|
if (Mixer::mixers().count() == 0)
|
|
{
|
|
QString text;
|
|
text = i18n("The last soundcard was unplugged.");
|
|
KMixToolBox::notification("MasterFallback", text);
|
|
}
|
|
recreateGUI(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Create a widget with an error message
|
|
* This widget shows an error message like "no mixers detected.
|
|
void KMixWindow::setErrorMixerWidget()
|
|
{
|
|
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
|
|
m_errorLabel = new QLabel( s,this );
|
|
m_errorLabel->setAlignment( Qt::AlignCenter );
|
|
m_errorLabel->setWordWrap(true);
|
|
m_errorLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
|
m_wsMixers->addTab( m_errorLabel, i18n("No soundcard found") );
|
|
}
|
|
*/
|
|
|
|
/**
|
|
*
|
|
*/
|
|
bool
|
|
KMixWindow::profileExists(QString guiProfileId)
|
|
{
|
|
for (int i = 0; i < m_wsMixers->count(); ++i)
|
|
{
|
|
KMixerWidget* kmw = qobject_cast<KMixerWidget*>(m_wsMixers->widget(i));
|
|
if (kmw && kmw->getGuiprof()->getId() == guiProfileId)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
KMixWindow::addMixerWidget(const QString& mixer_ID, QString guiprofId, int insertPosition)
|
|
{
|
|
kDebug() << "Add " << guiprofId;
|
|
GUIProfile* guiprof = GUIProfile::find(guiprofId);
|
|
if (guiprof != 0 && profileExists(guiprof->getId())) // TODO Bad place. Should be checked in the add-tab-dialog
|
|
return false; // already present => don't add again
|
|
Mixer *mixer = Mixer::findMixer(mixer_ID);
|
|
if (mixer == 0)
|
|
return false; // no such Mixer
|
|
|
|
// kDebug(67100) << "KMixWindow::addMixerWidget() " << mixer_ID << " is being added";
|
|
ViewBase::ViewFlags vflags = ViewBase::HasMenuBar;
|
|
if ((_actionShowMenubar == 0) || _actionShowMenubar->isChecked())
|
|
vflags |= ViewBase::MenuBarVisible;
|
|
if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Vertical)
|
|
vflags |= ViewBase::Horizontal;
|
|
else
|
|
vflags |= ViewBase::Vertical;
|
|
|
|
KMixerWidget *kmw = new KMixerWidget(mixer, this, vflags, guiprofId,
|
|
actionCollection());
|
|
/* A newly added mixer will automatically added at the top
|
|
* and thus the window title is also set appropriately */
|
|
|
|
/*
|
|
* Skip the name from the profile for now. I would at least have to do the '&' quoting for the tab label. But I am
|
|
* also not 100% sure whether the current name from the profile is any good - it does (likely) not even contain the
|
|
* card ID. This means you cannot distinguish between cards with an identical name.
|
|
*/
|
|
// QString tabLabel = guiprof->getName();
|
|
// if (tabLabel.isEmpty())
|
|
// QString tabLabel = kmw->mixer()->readableName(true);
|
|
|
|
QString tabLabel = kmw->mixer()->readableName(true);
|
|
|
|
m_dontSetDefaultCardOnStart = true; // inhibit implicit setting of m_defaultCardOnStart
|
|
|
|
if (insertPosition == -1)
|
|
m_wsMixers->addTab(kmw, tabLabel);
|
|
else
|
|
m_wsMixers->insertTab(insertPosition, kmw, tabLabel);
|
|
|
|
if (kmw->getGuiprof()->getId() == m_defaultCardOnStart)
|
|
{
|
|
m_wsMixers->setCurrentWidget(kmw);
|
|
}
|
|
|
|
updateTabsClosable();
|
|
m_dontSetDefaultCardOnStart = false;
|
|
|
|
kmw->loadConfig(KGlobal::config().data());
|
|
// Now force to read for new tabs, especially after hotplug. Note: Doing it here is bad design and possibly
|
|
// obsolete, as the backend should take care of upating itself.
|
|
kmw->mixer()->readSetFromHWforceUpdate();
|
|
return true;
|
|
}
|
|
|
|
void KMixWindow::updateTabsClosable()
|
|
{
|
|
// Ddo not allow to close the last view
|
|
m_wsMixers->setTabsClosable(m_wsMixers->count() > 1);
|
|
}
|
|
|
|
bool KMixWindow::queryClose()
|
|
{
|
|
GlobalConfigData& gcd = GlobalConfig::instance().data;
|
|
if (gcd.showDockWidget && !kapp->sessionSaving() )
|
|
{
|
|
// Hide (don't close and destroy), if docking is enabled. Except when session saving (shutdown) is in process.
|
|
hide();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Accept the close, if:
|
|
// The user has disabled docking
|
|
// or SessionSaving() is running
|
|
// kDebug(67100) << "close";
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void KMixWindow::hideOrClose()
|
|
{
|
|
GlobalConfigData& gcd = GlobalConfig::instance().data;
|
|
if (gcd.showDockWidget && m_dockWidget != 0)
|
|
{
|
|
// we can hide if there is a dock widget
|
|
hide();
|
|
}
|
|
else
|
|
{
|
|
// if there is no dock widget, we will quit
|
|
quit();
|
|
}
|
|
}
|
|
|
|
// internal helper to prevent code duplication in slotIncreaseVolume and slotDecreaseVolume
|
|
void
|
|
KMixWindow::increaseOrDecreaseVolume(bool increase)
|
|
{
|
|
Mixer* mixer = Mixer::getGlobalMasterMixer(); // only needed for the awkward construct below
|
|
if (mixer == 0)
|
|
return; // e.g. when no soundcard is available
|
|
std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
|
|
if (md.get() == 0)
|
|
return; // shouldn't happen, but lets play safe
|
|
|
|
Volume::VolumeTypeFlag volumeType = md->playbackVolume().hasVolume() ? Volume::Playback : Volume::Capture;
|
|
md->increaseOrDecreaseVolume(!increase, volumeType);
|
|
md->mixer()->commitVolumeChange(md);
|
|
|
|
showVolumeDisplay();
|
|
}
|
|
|
|
void
|
|
KMixWindow::slotIncreaseVolume()
|
|
{
|
|
increaseOrDecreaseVolume(true);
|
|
}
|
|
|
|
void
|
|
KMixWindow::slotDecreaseVolume()
|
|
{
|
|
increaseOrDecreaseVolume(false);
|
|
}
|
|
|
|
void
|
|
KMixWindow::showVolumeDisplay()
|
|
{
|
|
Mixer* mixer = Mixer::getGlobalMasterMixer();
|
|
if (mixer == 0)
|
|
return; // e.g. when no soundcard is available
|
|
std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
|
|
if (md.get() == 0)
|
|
return; // shouldn't happen, but lets play safe
|
|
|
|
// Current volume
|
|
// Setting not required any more, as the OSD updates the volume level itself
|
|
// Volume& vol = md->playbackVolume();
|
|
// osdWidget->setCurrentVolume(vol.getAvgVolumePercent(Volume::MALL),
|
|
// md->isMuted());
|
|
if (GlobalConfig::instance().data.showOSD)
|
|
{
|
|
osdWidget->show();
|
|
osdWidget->activateOSD(); //Enable the hide timer
|
|
}
|
|
//Center the OSD
|
|
QRect rect = KApplication::kApplication()->desktop()->screenGeometry(
|
|
QCursor::pos());
|
|
QSize size = osdWidget->sizeHint();
|
|
int posX = rect.x() + (rect.width() - size.width()) / 2;
|
|
int posY = rect.y() + 4 * rect.height() / 5;
|
|
osdWidget->setGeometry(posX, posY, size.width(), size.height());
|
|
}
|
|
|
|
/**
|
|
* Mutes the global master. (SLOT)
|
|
*/
|
|
void KMixWindow::slotMute()
|
|
{
|
|
Mixer* mixer = Mixer::getGlobalMasterMixer();
|
|
if (mixer == 0)
|
|
return; // e.g. when no soundcard is available
|
|
std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
|
|
if (md.get() == 0)
|
|
return; // shouldn't happen, but lets play safe
|
|
md->toggleMute();
|
|
mixer->commitVolumeChange(md);
|
|
showVolumeDisplay();
|
|
}
|
|
|
|
void
|
|
KMixWindow::quit()
|
|
{
|
|
// kDebug(67100) << "quit";
|
|
kapp->quit();
|
|
}
|
|
|
|
/**
|
|
* Shows the configuration dialog, with the "general" tab opened.
|
|
*/
|
|
void KMixWindow::showSettings()
|
|
{
|
|
KMixPrefDlg::getInstance()->switchToPage(KMixPrefDlg::PrefGeneral);
|
|
KMixPrefDlg::getInstance()->show();
|
|
}
|
|
|
|
void
|
|
KMixWindow::showHelp()
|
|
{
|
|
actionCollection()->action("help_contents")->trigger();
|
|
}
|
|
|
|
void
|
|
KMixWindow::showAbout()
|
|
{
|
|
actionCollection()->action("help_about_app")->trigger();
|
|
}
|
|
|
|
/**
|
|
* Apply the Preferences from the preferences dialog. Depending on what has been changed,
|
|
* the corresponding announcemnts are made.
|
|
*/
|
|
void KMixWindow::applyPrefs()
|
|
{
|
|
// -1- Determine what has changed ------------------------------------------------------------------
|
|
GlobalConfigData& config = GlobalConfig::instance().data;
|
|
GlobalConfigData& configBefore = configDataSnapshot;
|
|
|
|
bool labelsHasChanged = config.showLabels ^ configBefore.showLabels;
|
|
bool ticksHasChanged = config.showTicks ^ configBefore.showTicks;
|
|
|
|
bool dockwidgetHasChanged = config.showDockWidget ^ configBefore.showDockWidget;
|
|
|
|
bool toplevelOrientationHasChanged = config.getToplevelOrientation() != configBefore.getToplevelOrientation();
|
|
bool traypopupOrientationHasChanged = config.getTraypopupOrientation() != configBefore.getTraypopupOrientation();
|
|
kDebug() << "toplevelOrientationHasChanged=" << toplevelOrientationHasChanged <<
|
|
", config=" << config.getToplevelOrientation() << ", configBefore=" << configBefore.getToplevelOrientation();
|
|
kDebug() << "trayOrientationHasChanged=" << traypopupOrientationHasChanged <<
|
|
", config=" << config.getTraypopupOrientation() << ", configBefore=" << configBefore.getTraypopupOrientation();
|
|
|
|
// -2- Determine what effect the changes have ------------------------------------------------------------------
|
|
|
|
if (dockwidgetHasChanged || toplevelOrientationHasChanged
|
|
|| traypopupOrientationHasChanged)
|
|
{
|
|
// These might need a complete relayout => announce a ControlList change to rebuild everything
|
|
ControlManager::instance().announce(QString(), ControlChangeType::ControlList, QString("Preferences Dialog"));
|
|
}
|
|
else if (labelsHasChanged || ticksHasChanged)
|
|
{
|
|
ControlManager::instance().announce(QString(), ControlChangeType::GUI, QString("Preferences Dialog"));
|
|
}
|
|
// showOSD does not require any information. It reads on-the-fly from GlobalConfig.
|
|
|
|
|
|
// -3- Apply all changes ------------------------------------------------------------------
|
|
|
|
// this->repaint(); // make KMix look fast (saveConfig() often uses several seconds)
|
|
kapp->processEvents();
|
|
|
|
configDataSnapshot = GlobalConfig::instance().data; // create a new snapshot as all current changes are applied now
|
|
|
|
// Remove saveConfig() IF aa changes have been migrated to GlobalConfig.
|
|
// Currently there is still stuff like "show menu bar".
|
|
saveConfig();
|
|
}
|
|
|
|
void
|
|
KMixWindow::toggleMenuBar()
|
|
{
|
|
menuBar()->setVisible(_actionShowMenubar->isChecked());
|
|
}
|
|
|
|
void
|
|
KMixWindow::slotHWInfo()
|
|
{
|
|
KMessageBox::information(0, m_hwInfoString,
|
|
i18n("Mixer Hardware Information"));
|
|
}
|
|
|
|
void
|
|
KMixWindow::slotKdeAudioSetupExec()
|
|
{
|
|
static const QString kcmshell4 = QString::fromLatin1("kcmshell4");
|
|
static const QStringList kcmshell4args = QStringList() << QString::fromLatin1("kcmplayer");
|
|
if (!QProcess::startDetached(kcmshell4, kcmshell4args)) {
|
|
errorPopup(i18n("The kcmshell4 application is either not installed or not working."));
|
|
}
|
|
}
|
|
|
|
void
|
|
KMixWindow::errorPopup(const QString& msg)
|
|
{
|
|
QPointer<KDialog> dialog = new KDialog(this);
|
|
dialog->setButtons(KDialog::Ok);
|
|
dialog->setCaption(i18n("Error"));
|
|
QLabel* qlbl = new QLabel(msg);
|
|
dialog->setMainWidget(qlbl);
|
|
dialog->exec();
|
|
delete dialog;
|
|
kWarning() << msg;
|
|
}
|
|
|
|
void
|
|
KMixWindow::slotConfigureCurrentView()
|
|
{
|
|
KMixerWidget* mw = (KMixerWidget*) m_wsMixers->currentWidget();
|
|
ViewBase* view = 0;
|
|
if (mw)
|
|
view = mw->currentView();
|
|
if (view)
|
|
view->configureView();
|
|
}
|
|
|
|
void KMixWindow::slotSelectMasterClose(QObject*)
|
|
{
|
|
m_dsm = 0;
|
|
}
|
|
|
|
void KMixWindow::slotSelectMaster()
|
|
{
|
|
Mixer *mixer = Mixer::getGlobalMasterMixer();
|
|
if (mixer != 0)
|
|
{
|
|
if (!m_dsm) {
|
|
m_dsm = new DialogSelectMaster(Mixer::getGlobalMasterMixer(), this);
|
|
connect(m_dsm, SIGNAL(destroyed(QObject*)), this, SLOT(slotSelectMasterClose(QObject*)));
|
|
m_dsm->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
m_dsm->show();
|
|
}
|
|
m_dsm->raise();
|
|
m_dsm->activateWindow();
|
|
}
|
|
else
|
|
{
|
|
KMessageBox::error(0, i18n("No sound card is installed or currently plugged in."));
|
|
}
|
|
}
|
|
|
|
void
|
|
KMixWindow::newMixerShown(int /*tabIndex*/)
|
|
{
|
|
KMixerWidget* kmw = (KMixerWidget*) m_wsMixers->currentWidget();
|
|
if (kmw)
|
|
{
|
|
// I am using the app name as a PREFIX, as KMix is a single window app, and it is
|
|
// more helpful to the user to see "KDE Mixer" in a window list than a possibly cryptic
|
|
// soundcard name like "HDA ATI SB"
|
|
setWindowTitle(i18n("KDE Mixer") + " - " + kmw->mixer()->readableName());
|
|
if (!m_dontSetDefaultCardOnStart)
|
|
m_defaultCardOnStart = kmw->getGuiprof()->getId();
|
|
// As switching the tab does NOT mean switching the master card, we do not need to update dock icon here.
|
|
// It would lead to unnecesary flickering of the (complete) dock area.
|
|
|
|
// We only show the "Configure Channels..." menu item if the mixer is not dynamic
|
|
ViewBase* view = kmw->currentView();
|
|
QAction* action = actionCollection()->action(
|
|
"toggle_channels_currentview");
|
|
if (view && action)
|
|
action->setVisible(!view->isDynamic());
|
|
}
|
|
}
|
|
|
|
#include "moc_kmix.cpp"
|