diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c363493..3cf234c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,357 +1,354 @@ cmake_minimum_required(VERSION 3.5) project(kmix) set(PROJECT_VERSION "5.13.80") set(PROJECT_VERSION_MAJOR 5) add_definitions( -DTRANSLATION_DOMAIN=\"kmix\" ) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set (QT_MIN_VERSION "5.11.0") set (KF5_MIN_VERSION "5.48.0") set (PA_MIN_VERSION "0.9.16") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMPackageConfigHelpers) include(ECMInstallIcons) include(ECMQtDeclareLoggingCategory) include(GenerateExportHeader) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core DBus Gui Widgets Xml ) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Config ConfigWidgets Crash DBusAddons DocTools GlobalAccel I18n - IconThemes Notifications Solid WidgetsAddons WindowSystem XmlGui ) find_package(KF5Plasma ${KF5_MIN_VERSION}) set_package_properties(KF5Plasma PROPERTIES TYPE OPTIONAL PURPOSE "Required to build Plasma data engine" ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) # PulseAudio is an optional dependency # find_package(PulseAudio "${PA_MIN_VERSION}") # PulseAudio requires GLib2 if (PulseAudio_FOUND) find_package(GLIB2 REQUIRED) endif(PulseAudio_FOUND) # Canberra is an optional dependency find_package(Canberra) find_package(ALSA) include(CheckCXXSourceCompiles) check_cxx_source_compiles(" #include int main() { std::shared_ptr p; return 0; } " HAVE_STD_SHARED_PTR) check_cxx_source_compiles(" #include int main() { std::tr1::shared_ptr p; return 0; } " HAVE_STD_TR1_SHARED_PTR) configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h ) #################################################################################################### ########### compile definitions #################################################################### #################################################################################################### include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) # TODO: is the next line really needed? Nothing seems to use anything to do with tagging. if (MSVC) include_directories(${TAGLIB_INCLUDES}) endif (MSVC) if (ALSA_FOUND) set(HAVE_LIBASOUND2 TRUE) add_definitions(-DHAVE_LIBASOUND2) endif (ALSA_FOUND) if (PulseAudio_FOUND) add_definitions(-DHAVE_PULSE) include_directories(${PulseAudio_INCLUDE_DIRS}) include_directories(${GLIB2_INCLUDE_DIR}) endif (PulseAudio_FOUND) if (CANBERRA_FOUND) add_definitions(-DHAVE_CANBERRA) include_directories(${CANBERRA_INCLUDE_DIRS}) endif (CANBERRA_FOUND) #################################################################################################### ########### subdirectories ######################################################################### #################################################################################################### add_subdirectory(doc) add_subdirectory(pics) add_subdirectory(profiles) #add_subdirectory(tests) if (KF5Plasma_FOUND) add_subdirectory(plasma) endif () #################################################################################################### ########### definitions: logging ################################################################### #################################################################################################### ecm_qt_declare_logging_category(kmix_debug_SRCS HEADER kmix_debug.h IDENTIFIER KMIX_LOG CATEGORY_NAME org.kde.kmix) #################################################################################################### ########### definitions: DBus adaptor ############################################################## #################################################################################################### set(kmix_adaptor_SRCS dbus/dbusmixerwrapper.cpp dbus/dbusmixsetwrapper.cpp dbus/dbuscontrolwrapper.cpp ) qt5_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.control.xml dbus/dbuscontrolwrapper.h DBusControlWrapper ) qt5_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.mixer.xml dbus/dbusmixerwrapper.h DBusMixerWrapper ) qt5_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.mixset.xml dbus/dbusmixsetwrapper.h DBusMixSetWrapper ) install(FILES dbus/org.kde.kmix.control.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) install(FILES dbus/org.kde.kmix.mixer.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) install(FILES dbus/org.kde.kmix.mixset.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) #################################################################################################### ########### definitions: backends ################################################################## #################################################################################################### set(kmix_backend_SRCS backends/mixer_backend.cpp backends/mixer_mpris2.cpp ) if (HAVE_LIBASOUND2) set(kmix_backend_SRCS ${kmix_backend_SRCS} backends/mixer_alsa9.cpp ) endif (HAVE_LIBASOUND2) if (PulseAudio_FOUND) set(kmix_backend_SRCS ${kmix_backend_SRCS} backends/mixer_pulse.cpp ) endif (PulseAudio_FOUND) #################################################################################################### ########### target: kmixcore library ############################################################### #################################################################################################### set(kmixcore_SRCS core/MediaController.cpp core/mixertoolbox.cpp core/kmixdevicemanager.cpp core/ControlManager.cpp core/GlobalConfig.cpp core/MasterControl.cpp core/mixer.cpp core/mixset.cpp core/mixdevice.cpp core/mixdevicecomposite.cpp core/volume.cpp ) add_library(kmixcore SHARED ${kmixcore_SRCS} ${kmix_adaptor_SRCS} ${kmix_backend_SRCS} ${kmix_debug_SRCS} ) target_link_libraries(kmixcore PUBLIC Qt5::Core Qt5::Widgets PRIVATE Qt5::DBus KF5::I18n KF5::Solid PUBLIC KF5::ConfigCore KF5::ConfigGui ) set_target_properties(kmixcore PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) generate_export_header(kmixcore BASE_NAME kmixcore EXPORT_FILE_NAME kmixcore_export.h) if (HAVE_LIBASOUND2) target_link_libraries(kmixcore PRIVATE ${ALSA_LIBRARIES}) endif (HAVE_LIBASOUND2) if (PulseAudio_FOUND) target_link_libraries(kmixcore PRIVATE ${PulseAudio_LIBRARIES} ${PulseAudio_MAINLOOP_LIBRARY} ${GLIB2_LIBRARIES}) endif (PulseAudio_FOUND) if (CANBERRA_FOUND) target_link_libraries(kmixcore PRIVATE ${CANBERRA_LIBRARIES}) endif (CANBERRA_FOUND) install(TARGETS kmixcore DESTINATION ${KDE_INSTALL_LIBDIR} LIBRARY NAMELINK_SKIP) #################################################################################################### ########### target: kmixgui library ################################################################ #################################################################################################### set(kmixgui_SRCS gui/dialogbase.cpp gui/dialogstatesaver.cpp gui/kmixdockwidget.cpp gui/kmixprefdlg.cpp gui/viewbase.cpp gui/viewdockareapopup.cpp gui/viewsliders.cpp gui/mixdevicewidget.cpp gui/mdwslider.cpp gui/mdwenum.cpp gui/kmixerwidget.cpp gui/ksmallslider.cpp gui/verticaltext.cpp gui/volumeslider.cpp gui/kmixtoolbox.cpp gui/dialogaddview.cpp gui/dialogviewconfiguration.cpp gui/dialogselectmaster.cpp gui/dialogchoosebackends.cpp gui/guiprofile.cpp gui/toggletoolbutton.cpp ) add_library(kmixgui STATIC ${kmixgui_SRCS} ${kmix_debug_SRCS} ) target_link_libraries(kmixgui kmixcore Qt5::Core Qt5::Widgets KF5::I18n KF5::ConfigCore KF5::ConfigGui - KF5::IconThemes KF5::GlobalAccel KF5::Notifications KF5::XmlGui KF5::WindowSystem ) #################################################################################################### ########### target: kmix ########################################################################### #################################################################################################### set(kmix_SRCS apps/main.cpp apps/kmix.cpp apps/KMixApp.cpp ${kmix_debug_SRCS} ) add_executable(kmix ${kmix_SRCS}) target_link_libraries(kmix kmixcore kmixgui KF5::I18n - KF5::IconThemes KF5::DBusAddons KF5::GlobalAccel KF5::XmlGui KF5::Notifications KF5::WindowSystem ) install(TARGETS kmix ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES desktop/kmixui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kmix) install(PROGRAMS desktop/org.kde.kmix.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES desktop/org.kde.kmix.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES desktop/kmix_autostart.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) install(FILES desktop/kmix.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR} ) #################################################################################################### ########### target: kded_kmixd ##################################################################### #################################################################################################### set(kmixd_SRCS apps/kmixd.cpp ${kmix_debug_SRCS} ) add_library(kded_kmixd MODULE ${kmixd_SRCS}) set_target_properties(kded_kmixd PROPERTIES OUTPUT_NAME kmixd) kcoreaddons_desktop_to_json(kded_kmixd desktop/kmixd.desktop) target_link_libraries(kded_kmixd kmixcore KF5::I18n KF5::CoreAddons KF5::DBusAddons ) install(TARGETS kded_kmixd DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kded) #################################################################################################### ########### target: kmixctrl ####################################################################### #################################################################################################### set(kmixctrl_SRCS apps/kmixctrl.cpp ${kmix_debug_SRCS} ) add_executable(kmixctrl ${kmixctrl_SRCS}) target_link_libraries(kmixctrl kmixcore KF5::I18n KF5::CoreAddons ) install(TARGETS kmixctrl ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES desktop/kmixctrl_restore.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) #################################################################################################### ########### other installs ######################################################################### #################################################################################################### install(PROGRAMS apps/kmixremote DESTINATION ${KDE_INSTALL_BINDIR}) install(FILES desktop/restore_kmix_volumes.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) #################################################################################################### ########### end #################################################################################### #################################################################################################### feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/apps/kmix.cpp b/apps/kmix.cpp index 8b714684..d69ef1ae 100644 --- a/apps/kmix.cpp +++ b/apps/kmix.cpp @@ -1,1303 +1,1300 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 1996-2014 The KMix authors. Maintainer: Christian Esken * * 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 #include #include #include #include #include #include #include #include #include #include #include // include files for KDE #include -#include #include #include #include #include #include #include #include // 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 "kmix_debug.h" #include #include #include /* KMixWindow * Constructs a mixer window (KMix main window) */ KMixWindow::KMixWindow(bool invisible, bool reset) : KXmlGuiWindow(0, Qt::WindowFlags( KDE_DEFAULT_WINDOWFLAGS | Qt::WindowContextHelpButtonHint)), m_multiDriverMode(false), // -<- I never-ever want the multi-drivermode to be activated by accident m_autouseMultimediaKeys(true), m_dockWidget(), m_dsm(0), m_dontSetDefaultCardOnStart(false) { setObjectName(QStringLiteral("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 loadAndInitConfig(reset); // Load config before initMixer(), e.g. due to "MultiDriver" keyword initActionsLate(); // init actions that require a loaded config // TODO: Port to KF5 //KGlobal::locale()->insertCatalog(QLatin1String("kmix-controls")); initWidgets(); initPrefDlg(); DBusMixSetWrapper::initialize(this, QStringLiteral("/Mixers")); MixerToolBox::initMixer(m_multiDriverMode, m_backendFilter, true); KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance(); initActionsAfterInitMixer(); // init actions that require initialized mixer backend(s). recreateGUI(false, reset); if (m_wsMixers->count() < 1) { // Something is wrong. Perhaps a hardware or driver or backend change. Let KMix search harder recreateGUI(false, QString(), true, reset); } if (!qApp->isSessionRestored() ) // done by the session manager otherwise setInitialSize(); fixConfigAfterRead(); connect(theKMixDeviceManager, &KMixDeviceManager::plugged, this, &KMixWindow::plugged); connect(theKMixDeviceManager, &KMixDeviceManager::unplugged, this, &KMixWindow::unplugged); theKMixDeviceManager->initHotplug(); if (m_startVisible && !invisible) show(); // Started visible connect(qApp, SIGNAL(aboutToQuit()), SLOT(saveConfig()) ); ControlManager::instance().addListener( QString(), // All mixers (as the Global master Mixer might change) ControlManager::ControlList|ControlManager::MasterChanged, this, "KMixWindow"); // Send an initial volume refresh (otherwise all volumes are 0 until the next change) ControlManager::instance().announce(QString(), ControlManager::Volume, "Startup"); } KMixWindow::~KMixWindow() { ControlManager::instance().removeListener(this); delete m_dsm; // -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::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(ControlManager::ChangeType changeType) { switch (changeType) { case ControlManager::ControlList: case ControlManager::MasterChanged: updateDocking(); break; default: ControlManager::warnUnexpectedChangeType(changeType, this); break; } } void KMixWindow::initActions() { // file menu KStandardAction::quit(this, SLOT(quit()), actionCollection()); // settings menu _actionShowMenubar = KStandardAction::showMenubar(this, SLOT(toggleMenuBar()), actionCollection()); //actionCollection()->addAction(QStringLiteral( a->objectName()), a ); KStandardAction::preferences(this, SLOT(showSettings()), actionCollection()); KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), actionCollection()); QAction* action = actionCollection()->addAction(QStringLiteral("launch_kdesoundsetup")); action->setText(i18n("Audio Setup...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotKdeAudioSetupExec())); action = actionCollection()->addAction(QStringLiteral("hide_kmixwindow")); action->setText(i18n("Hide Mixer Window")); connect(action, SIGNAL(triggered(bool)), SLOT(hideOrClose())); actionCollection()->setDefaultShortcut(action, Qt::Key_Escape); action = actionCollection()->addAction(QStringLiteral("toggle_channels_currentview")); action->setText(i18n("Configure &Channels...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotConfigureCurrentView())); action = actionCollection()->addAction(QStringLiteral("select_master")); action->setText(i18n("Select Master Channel...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotSelectMaster())); action = actionCollection()->addAction(QStringLiteral("save_1")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_1); action->setText(i18n("Save volume profile 1")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes1())); action = actionCollection()->addAction(QStringLiteral("save_2")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_2); action->setText(i18n("Save volume profile 2")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes2())); action = actionCollection()->addAction(QStringLiteral("save_3")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_3); action->setText(i18n("Save volume profile 3")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes3())); action = actionCollection()->addAction(QStringLiteral("save_4")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_4); action->setText(i18n("Save volume profile 4")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes4())); action = actionCollection()->addAction(QStringLiteral("load_1")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_1); action->setText(i18n("Load volume profile 1")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes1())); action = actionCollection()->addAction(QStringLiteral("load_2")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_2); action->setText(i18n("Load volume profile 2")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes2())); action = actionCollection()->addAction(QStringLiteral("load_3")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_3); action->setText(i18n("Load volume profile 3")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes3())); action = actionCollection()->addAction(QStringLiteral("load_4")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_4); action->setText(i18n("Load volume profile 4")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes4())); createGUI(QLatin1String("kmixui.rc")); } void KMixWindow::initActionsLate() { if (m_autouseMultimediaKeys) { QAction* globalAction = actionCollection()->addAction(QStringLiteral("increase_volume")); globalAction->setText(i18n("Increase Volume")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeUp); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotIncreaseVolume())); globalAction = actionCollection()->addAction(QStringLiteral("decrease_volume")); globalAction->setText(i18n("Decrease Volume")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeDown); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotDecreaseVolume())); globalAction = actionCollection()->addAction(QStringLiteral("mute")); globalAction->setText(i18n("Mute")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeMute); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotMute())); } } void KMixWindow::initActionsAfterInitMixer() { // Only show the new tab widget if Pulseaudio is not used. Hint: The Pulseaudio backend always // runs with 4 fixed Tabs. if (!Mixer::pulseaudioPresent()) { - QPixmap cornerNewPM = KIconLoader::global()->loadScaledIcon("tab-new", KIconLoader::Toolbar, - devicePixelRatioF(), IconSize(KIconLoader::Toolbar)); QPushButton* _cornerLabelNew = new QPushButton(); - _cornerLabelNew->setIcon(cornerNewPM); + _cornerLabelNew->setIcon(QIcon::fromTheme("tab-new")); _cornerLabelNew->setToolTip(i18n("Add new view")); //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 QTabWidget(); m_wsMixers->setDocumentMode(true); setCentralWidget(m_wsMixers); m_wsMixers->setTabsClosable(false); connect(m_wsMixers, SIGNAL(tabCloseRequested(int)), SLOT(saveAndCloseView(int))); 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(KSharedConfig::openConfig(), "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(); #ifdef __GNUC_ #warn We must Sync here, or we will lose configuration data. The reson for that is unknown. #endif // TODO cesken The reason for not writing might be that we have multiple cascaded KConfig objects. I must migrate to KSharedConfig !!! KSharedConfig::openConfig()->sync(); qCDebug(KMIX_LOG) << "Saved config ... sync finished"; } void KMixWindow::saveBaseConfig() { GlobalConfig::instance().save(); KConfigGroup config(KSharedConfig::openConfig(), "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(false); config.writeEntry("MasterMixer", master.getCard()); config.writeEntry("MasterMixerDevice", master.getControl()); QString mixerIgnoreExpression = MixerToolBox::mixerIgnoreExpression(); config.writeEntry("MixerIgnoreExpression", mixerIgnoreExpression); qCDebug(KMIX_LOG) << "Base configuration saved"; } void KMixWindow::saveViewConfig() { QMap 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() ) { 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); KMixerWidget *mw = qobject_cast(w); if (mw!=nullptr) { // 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(KSharedConfig::openConfig().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(KSharedConfig::openConfig(), "Profiles"); QMap::const_iterator itEnd = mixerViews.constEnd(); for (QMap::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); qCDebug(KMIX_LOG) << "Save Profile List for " << mixerProfileKey << ", number of views is " << qslProfiles.count(); } qCDebug(KMIX_LOG) << "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; qCDebug(KMIX_LOG) << "Volume configuration saved"; } QString KMixWindow::getKmixctrlRcFilename(QString postfix) { QString kmixctrlRcFilename("kmixctrlrc"); if (!postfix.isEmpty()) { kmixctrlRcFilename.append(".").append(postfix); } return kmixctrlRcFilename; } void KMixWindow::loadAndInitConfig(bool reset) { if (!reset) { 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(KSharedConfig::openConfig(), "Global"); GlobalConfigData& gcd = GlobalConfig::instance().data; QList 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_configVersion = config.readEntry("ConfigVersion", 0); // WARNING Don't overwrite m_configVersion with the "correct" value, before having it // evaluated. Better only write that in saveBaseConfig() 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::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 ------------------------------------- // The following log is very helpful in bug reports. Please keep it. m_backendFilter = config.readEntry<>("Backends", QList()); qCDebug(KMIX_LOG) << "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 volumes as stored via saveVolumes() or the * execution of "kmixctrl --save" */ void KMixWindow::loadVolumes() { loadVolumes(QString()); } void KMixWindow::loadVolumes(QString postfix) { qCDebug(KMIX_LOG) << "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() { recreateGUI(true, false); } void KMixWindow::recreateGUI(bool saveConfig, bool reset) { recreateGUI(saveConfig, QString(), false, reset); } /** * 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, bool reset) { // -1- Remember which of the tabs is currently selected for restoration for re-insertion int oldTabPosition = m_wsMixers->currentIndex(); if (!reset && saveConfig) saveViewConfig(); // save the state before recreating // -2- RECREATE THE ALREADY EXISTING TABS ********************************** QHash mixerHasProfile; // -2a- Build a list of all active profiles in the main window (that means: from all tabs) QList activeGuiProfiles; for (int i = 0; i < m_wsMixers->count(); ++i) { KMixerWidget* kmw = dynamic_cast(m_wsMixers->widget(i)); if (kmw) { activeGuiProfiles.append(kmw->getGuiprof()); } } foreach ( GUIProfile* guiprof, activeGuiProfiles) { Mixer *mixer = Mixer::findMixer( guiprof->getMixerId() ); if ( mixer == 0 ) { qCCritical(KMIX_LOG) << "MixerToolBox::find() hasn't found the Mixer for the profile " << guiprof->getId(); continue; } mixerHasProfile[mixer] = true; KMixerWidget* kmw = findKMWforTab(guiprof->getId()); 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(KSharedConfig::openConfig(), "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 { GUIProfile* guiprof = 0; if (reset) { guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ### } if ( guiprof != 0 ) { guiprof->setDirty(); // All fallback => dirty addMixerWidget(mixer->id(), guiprof->getId(), -1); continue; } } // ========================================================================================= // The trivial cases have not added anything => Look at [Profiles] in config file QStringList profileList = pconfig.readEntry( mixer->id(), QStringList() ); bool allProfilesRemovedByUser = pconfig.hasKey(mixer->id()) && profileList.isEmpty(); if (allProfilesRemovedByUser) { continue; // User has explicitly hidden the views => do no further checks } { bool atLeastOneProfileWasAdded = false; foreach ( QString profileId, profileList) { // This handles the profileList form the kmixrc qCDebug(KMIX_LOG) << "Searching for GUI profile" << profileId; GUIProfile* guiprof = GUIProfile::find(mixer, profileId, true, false);// ### Card specific profile ### if (guiprof==nullptr) { qCWarning(KMIX_LOG) << "Cannot load profile" << profileId; if (profileId.startsWith("MPRIS2.")) { profileId = "MPRIS2.default"; qCDebug(KMIX_LOG) << "For MPRIS2 falling back to" << profileId; guiprof = GUIProfile::find(mixer, profileId, true, false); } } if (guiprof!=nullptr) { addMixerWidget(mixer->id(), guiprof->getId(), -1); atLeastOneProfileWasAdded = true; } } if (atLeastOneProfileWasAdded) { // Continue continue; } } // ========================================================================================= // Neither trivial cases have added something, nor the anything => Look at [Profiles] in config file // The we_need_a_fallback case is a bit tricky. Please ask the author (cesken) before even considering to change the code. bool mixerIdMatch = mixerId.isEmpty() || (mixer->id() == mixerId); bool thisMixerShouldBeForced = forceNewTab && mixerIdMatch; bool we_need_a_fallback = mixerIdMatch && thisMixerShouldBeForced; 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: qCDebug(KMIX_LOG) << "Attempting to find a card-specific GUI Profile for the mixer " << mixer->id(); GUIProfile* guiprof = GUIProfile::find(mixer, QString("default"), false, false);// ### Card specific profile ### if ( guiprof == 0 ) { qCDebug(KMIX_LOG) << "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) { qCDebug(KMIX_LOG) << "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 { qCCritical(KMIX_LOG) << "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 inaccessible 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 = qobject_cast(m_wsMixers->widget(i)); if (kmw->getGuiprof()->getId() == kmwId) { return kmw; } } return 0; } void KMixWindow::newView() { if (Mixer::mixers().empty()) { qCCritical(KMIX_LOG) << "Trying to create a View, but no Mixer exists"; return; // should never happen } Mixer *mixer = Mixer::mixers()[0]; QPointer 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); qCDebug(KMIX_LOG) << ">>> mixer = " << mixerId << " -> " << mixer; GUIProfile* guiprof = GUIProfile::find(mixer, profileName, false, false); if (guiprof == nullptr) { guiprof = GUIProfile::find(mixer, profileName, false, true); } if (guiprof == nullptr) { KMessageBox::sorry(this, i18n("Cannot add view - GUIProfile is invalid."), i18n("Error")); } else { bool ret = addMixerWidget(mixer->id(), guiprof->getId(), -1); if (!ret) { KMessageBox::sorry(this, i18n("Cannot add view - View already exists."), i18n("Error")); } } delete dav; } //qCDebug(KMIX_LOG) << "Exit"; } /** * Save the view and close it * * @arg idx The index in the TabWidget */ void KMixWindow::saveAndCloseView(int idx) { qCDebug(KMIX_LOG) << "Enter"; QWidget *w = m_wsMixers->widget(idx); KMixerWidget* kmw = ::qobject_cast(w); if (kmw) { kmw->saveConfig(KSharedConfig::openConfig().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; } qCDebug(KMIX_LOG) << "Exit"; } void KMixWindow::fixConfigAfterRead() { KConfigGroup grp(KSharedConfig::openConfig(), "Global"); unsigned int configVersion = grp.readEntry("ConfigVersion", 0); if (configVersion < 3) { // Fix the "double Base" bug, by deleting all groups starting with "View.Base.Base.". // The group has been copied over by KMixToolBox::loadView() for all soundcards, so // we should be fine now QStringList cfgGroups = KSharedConfig::openConfig()->groupList(); QStringListIterator it(cfgGroups); while (it.hasNext()) { QString groupName = it.next(); if (groupName.indexOf("View.Base.Base") == 0) { qCDebug(KMIX_LOG) << "Fixing group " << groupName; KConfigGroup buggyDevgrpCG(KSharedConfig::openConfig(), groupName); buggyDevgrpCG.deleteGroup(); } // remove buggy group } // for all groups } // if config version < 3 } void KMixWindow::plugged(const char *driverName, const QString &udi, int dev) { qCDebug(KMIX_LOG) << "dev" << dev << "driver" << driverName << "udi" << udi; Mixer *mixer = new Mixer(QString::fromLocal8Bit(driverName), dev); if (mixer!=nullptr) { if (MixerToolBox::possiblyAddMixer(mixer)) { qCDebug(KMIX_LOG) << "adding mixer id" << mixer->id() << "name" << mixer->readableName(); recreateGUI(true, mixer->id(), true, false); } else qCWarning(KMIX_LOG) << "Cannot add mixer to GUI"; } } void KMixWindow::unplugged(const QString &udi) { qCDebug(KMIX_LOG) << "udi" << udi; for (int i = 0; i < Mixer::mixers().count(); ++i) { Mixer *mixer = (Mixer::mixers())[i]; // qCDebug(KMIX_LOG) << "Try Match with:" << mixer->udi(); if (mixer->udi() == udi) { qCDebug(KMIX_LOG) << "Removing mixer"; bool globalMasterMixerDestroyed = (mixer == Mixer::getGlobalMasterMixer()); // Part 1: Remove tab from GUI for (int i = 0; i < m_wsMixers->count(); ++i) { QWidget *w = m_wsMixers->widget(i); KMixerWidget* kmw = ::qobject_cast(w); if (kmw && kmw->mixer() == mixer) { saveAndCloseView(i); i = -1; // Restart loop from scratch (indices are most likely invalidated at removeTab() ) } } // Part 2: Remove mixer from known list MixerToolBox::removeMixer(mixer); // Part 3: Check whether the Global Master disappeared, // and select a new one if necessary shared_ptr 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) { shared_ptr 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, false); 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 = dynamic_cast(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) { qCDebug(KMIX_LOG) << "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 // qCDebug(KMIX_LOG) << "KMixWindow::addMixerWidget() " << mixer_ID << " is being added"; ViewBase::ViewFlags vflags = ViewBase::HasMenuBar; if ((_actionShowMenubar == 0) || _actionShowMenubar->isChecked()) vflags |= ViewBase::MenuBarVisible; 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(KSharedConfig::openConfig().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 updating itself. kmw->mixer()->readSetFromHWforceUpdate(); return true; } void KMixWindow::updateTabsClosable() { // Pulseaudio runs with 4 fixed tabs - don't allow to close them. // Also do not allow to close the last view m_wsMixers->setTabsClosable(!Mixer::pulseaudioPresent() && m_wsMixers->count() > 1); } bool KMixWindow::queryClose() { GlobalConfigData& gcd = GlobalConfig::instance().data; if (gcd.showDockWidget && !qApp->isSavingSession() ) { // 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 // qCDebug(KMIX_LOG) << "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 shared_ptr 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 shared_ptr md = Mixer::getGlobalMasterMD(); if (md.get() == 0) return; // shouldn't happen, but lets play safe if (GlobalConfig::instance().data.showOSD) { QDBusMessage msg = QDBusMessage::createMethodCall( "org.kde.plasmashell", "/org/kde/osdService", "org.kde.osdService", "volumeChanged" ); int currentVolume = 0; if (!md->isMuted()) { currentVolume = md->playbackVolume().getAvgVolumePercent(Volume::MALL); } msg.setArguments(QList() << currentVolume); QDBusConnection::sessionBus().asyncCall(msg); } } /** * Mutes the global master. (SLOT) */ void KMixWindow::slotMute() { Mixer* mixer = Mixer::getGlobalMasterMixer(); if (mixer == 0) return; // e.g. when no soundcard is available shared_ptr md = Mixer::getGlobalMasterMD(); if (md.get() == 0) return; // shouldn't happen, but lets play safe md->toggleMute(); mixer->commitVolumeChange(md); showVolumeDisplay(); } void KMixWindow::quit() { // qCDebug(KMIX_LOG) << "quit"; qApp->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 announcements 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(); qCDebug(KMIX_LOG) << "toplevelOrientationHasChanged=" << toplevelOrientationHasChanged << ", config=" << config.getToplevelOrientation() << ", configBefore=" << configBefore.getToplevelOrientation(); qCDebug(KMIX_LOG) << "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(), ControlManager::ControlList, QString("Preferences Dialog")); } else if (labelsHasChanged || ticksHasChanged) { ControlManager::instance().announce(QString(), ControlManager::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) qApp->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::slotKdeAudioSetupExec() { forkExec(QStringList() << "kcmshell5" << "kcm_phonon"); } void KMixWindow::forkExec(const QStringList& args) { int pid = KProcess::startDetached(args); if (pid == 0) { KMessageBox::error(this, i18n("The helper application is either not installed or not working.\n\n%1", args.join(QLatin1String(" ")))); } } void KMixWindow::slotConfigureCurrentView() { KMixerWidget *mw = qobject_cast(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 = qobject_cast(m_wsMixers->currentWidget()); if (kmw!=nullptr) { // 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". // Reformatted for KF5 so as to not say "KDE" // and so that there are not two different dashes. setWindowTitle(i18n("Mixer (%1)", 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()); } } diff --git a/gui/dialogselectmaster.cpp b/gui/dialogselectmaster.cpp index 02640deb..baf4ebae 100644 --- a/gui/dialogselectmaster.cpp +++ b/gui/dialogselectmaster.cpp @@ -1,232 +1,229 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * 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 "gui/dialogselectmaster.h" #include #include #include #include -#include #include #include "core/ControlManager.h" #include "core/mixdevice.h" #include "core/mixer.h" DialogSelectMaster::DialogSelectMaster( Mixer *mixer, QWidget *parent ) : DialogBase( parent ) { setWindowTitle(i18n("Select Master Channel")); if ( Mixer::mixers().count() > 0 ) setButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); else { setButtons(QDialogButtonBox::Cancel); } m_channelSelector = 0; createWidgets(mixer); // Open with Mixer Hardware #0 } /** * Create basic widgets of the Dialog. */ void DialogSelectMaster::createWidgets(Mixer *ptr_mixer) { QWidget *mainFrame = new QWidget(this); setMainWidget(mainFrame); QVBoxLayout *layout = new QVBoxLayout(mainFrame); if ( Mixer::mixers().count() > 1 ) { // More than one Mixer => show Combo-Box to select Mixer // Mixer widget line QHBoxLayout* mixerNameLayout = new QHBoxLayout(); layout->addLayout( mixerNameLayout ); mixerNameLayout->setMargin(0); mixerNameLayout->setSpacing(DialogBase::horizontalSpacing()); QLabel *qlbl = new QLabel( i18n("Current mixer:"), mainFrame ); mixerNameLayout->addWidget(qlbl); qlbl->setFixedHeight(qlbl->sizeHint().height()); m_cMixer = new QComboBox(mainFrame); m_cMixer->setObjectName( QLatin1String( "mixerCombo" ) ); m_cMixer->setFixedHeight(m_cMixer->sizeHint().height()); connect( m_cMixer, SIGNAL(activated(int)), this, SLOT(createPageByID(int)) ); for( int i =0; i md = mixer->getLocalMasterMD(); const QString iconName = (md!=nullptr) ? md->iconName() : "media-playback-start"; m_cMixer->addItem(QIcon::fromTheme(iconName), mixer->readableName(), mixer->id() ); } // end for all_Mixers // Make the current Mixer the current item in the ComboBox int findIndex = m_cMixer->findData( ptr_mixer->id() ); if ( findIndex != -1 ) m_cMixer->setCurrentIndex( findIndex ); m_cMixer->setToolTip( i18n("Current mixer" ) ); mixerNameLayout->addWidget(m_cMixer, 1); layout->addSpacing(DialogBase::verticalSpacing()); } // end if (more_than_1_Mixer) if ( Mixer::mixers().count() > 0 ) { QLabel *qlbl = new QLabel( i18n("Select the channel representing the master volume:"), mainFrame ); layout->addWidget(qlbl); createPage(ptr_mixer); connect(this, SIGNAL(accepted()), this, SLOT(apply())); } else { QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), mainFrame ); layout->addWidget(qlbl); } } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogSelectMaster::createPageByID(int mixerId) { QString mixer_id = m_cMixer->itemData(mixerId).toString(); Mixer * mixer = Mixer::findMixer(mixer_id); if ( mixer != NULL ) createPage(mixer); } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogSelectMaster::createPage(Mixer* mixer) { /** --- Reset page ----------------------------------------------- * In case the user selected a new Mixer via m_cMixer, we need * to remove the stuff created on the last call. */ // delete the list widget. // This should automatically remove all contained items. delete m_channelSelector; /** Reset page end -------------------------------------------------- */ QWidget *mainFrame = mainWidget(); QVBoxLayout *layout = qobject_cast(mainFrame->layout()); Q_ASSERT(layout!=nullptr); m_channelSelector = new QListWidget(mainFrame); #ifndef QT_NO_ACCESSIBILITY m_channelSelector->setAccessibleName( i18n("Select Master Channel") ); #endif m_channelSelector->setSelectionMode(QAbstractItemView::SingleSelection); m_channelSelector->setDragEnabled(false); m_channelSelector->setAlternatingRowColors(true); layout->addWidget(m_channelSelector); // shared_ptr master = mixer->getLocalMasterMD(); // QString masterKey = ( master.get() != 0 ) ? master->id() : "----noMaster---"; // Use non-matching name as default const MixSet& mixset = mixer->getMixSet(); MixSet& mset = const_cast(mixset); MasterControl mc = mixer->getGlobalMasterPreferred(false); QString masterKey = mc.getControl(); if (!masterKey.isEmpty() && !mset.get(masterKey)) { shared_ptr master = mixer->getLocalMasterMD(); if (master.get() != 0) masterKey = master->id(); } int msetCount = 0; for (int i = 0; i < mset.count(); ++i) { shared_ptr md = mset[i]; if ( md->playbackVolume().hasVolume() ) ++msetCount; } if (msetCount > 0 && !mixer->isDynamic()) { QString mdName = i18n("Automatic (%1 recommendation)", mixer->getDriverName()); - QPixmap icon = KIconLoader::global()->loadScaledIcon("audio-volume-high", KIconLoader::Small, devicePixelRatioF(), IconSize(KIconLoader::Small)); - QListWidgetItem *item = new QListWidgetItem(icon, mdName, m_channelSelector); + auto *item = new QListWidgetItem(QIcon::fromTheme("audio-volume-high"), mdName, m_channelSelector); item->setData(Qt::UserRole, QString()); // ID here: see apply(), empty String => Automatic if (masterKey.isEmpty()) m_channelSelector->setCurrentItem(item); } // Populate ListView with the MixDevice's having a playbakc volume (excludes pure capture controls and pure enum's) for (int i = 0; i < mset.count(); ++i) { shared_ptr md = mset[i]; if ( md->playbackVolume().hasVolume() ) { QString mdName = md->readableName(); - QPixmap icon = KIconLoader::global()->loadScaledIcon(md->iconName(), KIconLoader::Small, devicePixelRatioF(), IconSize(KIconLoader::Small)); - QListWidgetItem *item = new QListWidgetItem(icon, mdName, m_channelSelector); + auto *item = new QListWidgetItem(QIcon::fromTheme(md->iconName()), mdName, m_channelSelector); item->setData(Qt::UserRole, md->id()); // ID here: see apply() if ( md->id() == masterKey ) { // select the current master m_channelSelector->setCurrentItem(item); } } } setButtonEnabled(QDialogButtonBox::Ok, m_channelSelector->count()>0); } void DialogSelectMaster::apply() { Mixer *mixer = 0; if ( Mixer::mixers().count() == 1 ) { // only one mxier => no combo box => take first entry mixer = (Mixer::mixers())[0]; } else if ( Mixer::mixers().count() > 1 ) { // find mixer that is currently active in the ComboBox int idx = m_cMixer->currentIndex(); QString mixer_id = m_cMixer->itemData(idx).toString(); mixer = Mixer::findMixer(mixer_id); } if ( mixer == 0 ) return; // User must have unplugged everything QList items = m_channelSelector->selectedItems(); if (items.count()==1) { QListWidgetItem *item = items.first(); QString control_id = item->data(Qt::UserRole).toString(); mixer->setLocalMasterMD( control_id ); Mixer::setGlobalMaster(mixer->id(), control_id, true); ControlManager::instance().announce(mixer->id(), ControlManager::MasterChanged, QString("Select Master Dialog")); } } diff --git a/gui/dialogviewconfiguration.cpp b/gui/dialogviewconfiguration.cpp index f8222244..4939e8b3 100644 --- a/gui/dialogviewconfiguration.cpp +++ b/gui/dialogviewconfiguration.cpp @@ -1,374 +1,372 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * 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 "dialogviewconfiguration.h" #include #include #include #include #include -#include #include #include "gui/guiprofile.h" #include "gui/mixdevicewidget.h" #include "core/ControlManager.h" #include "core/mixdevice.h" #include "core/mixer.h" /** * Standard constructor. */ -DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, qreal devicePixelRatio, const QString &id, bool shown, const QString &name, int splitted, const QString& iconName) : - QListWidgetItem(parent), _id(id), _shown(shown), _name(name), _splitted(splitted), _iconName(iconName), _devicePixelRatio(devicePixelRatio) +DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, const QString &id, bool shown, const QString &name, int splitted, const QString& iconName) : + QListWidgetItem(parent), _id(id), _shown(shown), _name(name), _splitted(splitted), _iconName(iconName) { refreshItem(); } /** * Deserializing constructor. Used for DnD. */ -DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, qreal devicePixelRatio, QDataStream &s) +DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, QDataStream &s) : QListWidgetItem(parent) - , _devicePixelRatio(devicePixelRatio) { s >> _id; s >> _shown; s >> _name; s >> _splitted; s >> _iconName; refreshItem(); } void DialogViewConfigurationItem::refreshItem() { setFlags((flags() | Qt::ItemIsDragEnabled) & ~Qt::ItemIsDropEnabled); setText(_name); - setIcon(KIconLoader::global()->loadScaledIcon( _iconName, KIconLoader::Small, _devicePixelRatio, IconSize(KIconLoader::Toolbar) )); + setIcon(QIcon::fromTheme(_iconName)); setData(Qt::ToolTipRole, _id); // a hack. I am giving up to do it right setData(Qt::DisplayRole, _name); } /** * Serializer. Used for DnD. */ QDataStream &DialogViewConfigurationItem::serialize(QDataStream &s) const { s << _id; s << _shown; s << _name; s << _splitted; s << _iconName; return (s); } DialogViewConfigurationWidget::DialogViewConfigurationWidget(QWidget *parent) : QListWidget(parent), m_activeList(true) { setDragDropMode(QAbstractItemView::DragDrop); setDropIndicatorShown(true); setAcceptDrops(true); setSelectionMode(QAbstractItemView::SingleSelection); setDragEnabled(true); viewport()->setAcceptDrops(true); setAlternatingRowColors(true); } QMimeData* DialogViewConfigurationWidget::mimeData(const QList items) const { if (items.isEmpty()) return 0; QMimeData* mimedata = new QMimeData(); QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); // we only support single selection DialogViewConfigurationItem* item = static_cast(items.first()); stream << *item; bool active = isActiveList(); mimedata->setData("application/x-kde-action-list", data); mimedata->setData("application/x-kde-source-treewidget", active ? "active" : "inactive"); return mimedata; } bool DialogViewConfigurationWidget::dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction /*action*/) { const QByteArray data = mimeData->data("application/x-kde-action-list"); if (data.isEmpty()) return false; QDataStream stream(data); - DialogViewConfigurationItem* item = new DialogViewConfigurationItem(nullptr, devicePixelRatioF(), stream); + DialogViewConfigurationItem* item = new DialogViewConfigurationItem(nullptr, stream); emit dropped(this, index, item); return true; } DialogViewConfiguration::DialogViewConfiguration(QWidget *parent, ViewBase &view) : DialogBase(parent), _view(view) { setWindowTitle( i18n( "Configure Channels" ) ); setButtons( QDialogButtonBox::Ok|QDialogButtonBox::Cancel ); QWidget *frame = new QWidget( this ); frame->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); setMainWidget( frame ); // The _layout will hold two items: The title and the Drag-n-Drop area QVBoxLayout *layout = new QVBoxLayout(frame); // --- HEADER --- QLabel *qlb = new QLabel( i18n("Configure the visible channels. Drag icons between the lists to update."), frame ); layout->addWidget(qlb); _glayout = new QGridLayout(); _glayout->setMargin(0); layout->addLayout(_glayout); createPage(); } /** * Drop an item from one list to the other. */ void DialogViewConfiguration::slotDropped(DialogViewConfigurationWidget *list, int index, DialogViewConfigurationItem *item) { //qCDebug(KMIX_LOG) << "dropped item (index" << index << "): " << item->_id << item->_shown << item->_name << item->_splitted << item->_iconName; list->insertItem(index, item); } void DialogViewConfiguration::addSpacer(int row, int col) { QWidget *dummy = new QWidget(); dummy->setFixedWidth(4); _glayout->addWidget(dummy,row,col); } void DialogViewConfiguration::moveSelection(DialogViewConfigurationWidget *from, DialogViewConfigurationWidget *to) { const QList sel = from->selectedItems(); from->selectionModel()->clearSelection(); foreach (QListWidgetItem *item, sel) { from->takeItem(from->row(item)); to->addItem(item); to->setCurrentItem(item); } } void DialogViewConfiguration::moveSelectionToActiveList() { moveSelection(_qlwInactive, _qlwActive); } void DialogViewConfiguration::moveSelectionToInactiveList() { moveSelection(_qlwActive, _qlwInactive); } void DialogViewConfiguration::selectionChangedActive() { moveRightButton->setEnabled(!_qlwActive->selectedItems().isEmpty()); moveLeftButton->setEnabled(false); } void DialogViewConfiguration::selectionChangedInactive() { moveLeftButton->setEnabled(!_qlwInactive->selectedItems().isEmpty()); moveRightButton->setEnabled(false); } /** * Create basic widgets of the Dialog. */ void DialogViewConfiguration::createPage() { QLabel *l1 = new QLabel( i18n("Visible channels:") ); _glayout->addWidget(l1,0,0); QLabel *l2 = new QLabel( i18n("Available channels:") ); _glayout->addWidget(l2,0,6); QWidget *frame = mainWidget(); _qlwInactive = new DialogViewConfigurationWidget(frame); _qlwInactive->setDragDropMode(QAbstractItemView::DragDrop); _qlwInactive->setActiveList(false); _glayout->addWidget(_qlwInactive,1,6); connect(_qlwInactive, &DialogViewConfigurationWidget::dropped, this, &DialogViewConfiguration::slotDropped); addSpacer(1,1); const QIcon& icon = QIcon::fromTheme( QLatin1String( "arrow-left" )); moveLeftButton = new QPushButton(icon, ""); moveLeftButton->setEnabled(false); moveLeftButton->setToolTip(i18n("Move the selected channel to the visible list")); _glayout->addWidget(moveLeftButton,1,2); connect(moveLeftButton, &QPushButton::clicked, this, &DialogViewConfiguration::moveSelectionToActiveList); addSpacer(1,3); const QIcon& icon2 = QIcon::fromTheme( QLatin1String( "arrow-right" )); moveRightButton = new QPushButton(icon2, ""); moveRightButton->setEnabled(false); moveRightButton->setToolTip(i18n("Move the selected channel to the available (hidden) list")); _glayout->addWidget(moveRightButton,1,4); connect(moveRightButton, &QPushButton::clicked, this, &DialogViewConfiguration::moveSelectionToInactiveList); addSpacer(1,5); _qlwActive = new DialogViewConfigurationWidget(frame); _glayout->addWidget(_qlwActive,1,0); connect(_qlwActive, &DialogViewConfigurationWidget::dropped, this, &DialogViewConfiguration::slotDropped); // --- CONTROLS IN THE GRID ------------------------------------ //QPalette::ColorRole bgRole; const int num = _view.mixDeviceCount(); for (int i = 0; i(_view.mixDeviceAt(i)); if (mdw==nullptr) continue; //if ( i%2 == 0) bgRole = QPalette::Base; else bgRole = QPalette::AlternateBase; const shared_ptr md = mdw->mixDevice(); const QString mdName = md->readableName(); int splitted = -1; if ( ! md->isEnum() ) { splitted = ( md->playbackVolume().count() > 1) || ( md->captureVolume().count() > 1 ) ; } //qCDebug(KMIX_LOG) << "add DialogViewConfigurationItem: " << mdName << " visible=" << mdw->isVisible() << "splitted=" << splitted; - auto *item = new DialogViewConfigurationItem(nullptr, devicePixelRatioF(), md->id(), true, mdName, splitted, mdw->mixDevice()->iconName()); + auto *item = new DialogViewConfigurationItem(nullptr, md->id(), true, mdName, splitted, mdw->mixDevice()->iconName()); if (mdw->isVisible()) _qlwActive->addItem(item); else _qlwInactive->addItem(item); } // for all MDW's connect(_qlwInactive, &QListWidget::itemSelectionChanged, this, &DialogViewConfiguration::selectionChangedInactive); connect(_qlwActive, &QListWidget::itemSelectionChanged, this, &DialogViewConfiguration::selectionChangedActive); updateGeometry(); connect(this, &QDialog::accepted, this, &DialogViewConfiguration::apply); #ifndef QT_NO_ACCESSIBILITY moveLeftButton->setAccessibleName( i18n("Show the selected channel") ); moveRightButton->setAccessibleName( i18n("Hide the selected channel") ); _qlwActive->setAccessibleName( i18n("Visible channels") ); _qlwInactive->setAccessibleName( i18n("Available channels") ); #endif } void DialogViewConfiguration::apply() { // --- We have a 3-Step Apply of the Changes ------------------------------- // -1- Update view and profile ***************************************** GUIProfile* prof = _view.guiProfile(); const GUIProfile::ControlSet &oldControlset = prof->getControls(); GUIProfile::ControlSet newControlset; QAbstractItemModel* model; model = _qlwActive->model(); prepareControls(model, true, oldControlset, newControlset); model = _qlwInactive->model(); prepareControls(model, false, oldControlset, newControlset); // -2- Copy all mandatory "catch-all" controls form the old to the new ControlSet ******* foreach ( ProfControl* pctl, oldControlset) { if ( pctl->isMandatory() ) { ProfControl* newCtl = new ProfControl(*pctl); // The user has selected controls => mandatory controls (RegExp templates) should not been shown any longer newCtl->setVisibility(GuiVisibility::Never); newControlset.push_back(newCtl); } } prof->setControls(newControlset); prof->finalizeProfile(); prof->setDirty(); // --- Step 3: Tell the view, that it has changed (probably it needs some "polishing" --- if ( _view.getMixers().size() == 1 ) ControlManager::instance().announce(_view.getMixers().first()->id(), ControlManager::ControlList, QString("View Configuration Dialog")); else ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("View Configuration Dialog")); } void DialogViewConfiguration::prepareControls(QAbstractItemModel* model, bool isActiveView, const GUIProfile::ControlSet &oldCtlSet, GUIProfile::ControlSet &newCtlSet) { const int numRows = model->rowCount(); const int num = _view.mixDeviceCount(); for (int row = 0; row < numRows; ++row) { // -1- Extract the value from the model *************************** QModelIndex index = model->index(row, 0); QVariant vdci; vdci = model->data(index, Qt::ToolTipRole); // TooltipRole stores the ID (well, thats not really clean design, but it works) QString ctlId = vdci.toString(); // -2- Find the mdw, und update it ************************** for (int i = 0; i(_view.mixDeviceAt(i)); if (mdw==nullptr) continue; if ( mdw->mixDevice()->id() == ctlId ) { mdw->setVisible(isActiveView); break; } // mdw was found } // find mdw // -3- Insert it in the new ControlSet ************************** // qCDebug(KMIX_LOG) << "Should add to new ControlSet: " << ctlId; foreach ( ProfControl* control, oldCtlSet) { //qCDebug(KMIX_LOG) << " checking " << control->id; QRegExp idRegexp(control->id()); if ( ctlId.contains(idRegexp) ) { // found. Create a copy ProfControl* newCtl = new ProfControl(*control); newCtl->setId('^' + ctlId + '$'); // Replace the (possible generic) regexp by the actual ID // We have made this an an actual control. As it is derived (from e.g. ".*") it is NOT mandatory. newCtl->setMandatory(false); newCtl->setVisible(isActiveView); newCtlSet.push_back(newCtl); // qCDebug(KMIX_LOG) << "Added to new ControlSet (done): " << newCtl->id; break; } } } } diff --git a/gui/dialogviewconfiguration.h b/gui/dialogviewconfiguration.h index d4094e53..fb8329b3 100644 --- a/gui/dialogviewconfiguration.h +++ b/gui/dialogviewconfiguration.h @@ -1,145 +1,144 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright Christian Esken * * 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. */ #ifndef DIALOGVIEWCONFIGURATION_H #define DIALOGVIEWCONFIGURATION_H // Qt #include class QLabel; #include class QHBoxLayout; #include #include class QScrollArea; class QVBoxLayout; // Qt DND #include #include // KMix #include "dialogbase.h" #include "gui/guiprofile.h" #include "viewbase.h" class DialogViewConfigurationItem : public QListWidgetItem { public: - DialogViewConfigurationItem(QListWidget *parent, qreal devicePixelRatio, QDataStream &s); - DialogViewConfigurationItem(QListWidget *parent, qreal devicePixelRatio, const QString &id, bool shown, const QString &name, int splitted, const QString &iconName); + DialogViewConfigurationItem(QListWidget *parent, QDataStream &s); + DialogViewConfigurationItem(QListWidget *parent, const QString &id, bool shown, const QString &name, int splitted, const QString &iconName); ~DialogViewConfigurationItem() = default; private: void refreshItem(); QDataStream &serialize(QDataStream &s) const; friend QDataStream &operator<<(QDataStream &s, const DialogViewConfigurationItem &item) { return (item.serialize(s)); } private: // TODO: are '_shown', '_splitted' and '_iconName' actually used? QString _id; bool _shown; QString _name; int _splitted; QString _iconName; - qreal _devicePixelRatio; }; class DialogViewConfigurationWidget : public QListWidget { Q_OBJECT public: explicit DialogViewConfigurationWidget(QWidget *parent=0); void setActiveList(bool isActiveList) { m_activeList = isActiveList; } bool isActiveList() const { return m_activeList; }; Q_SIGNALS: void dropped(DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item); protected: QMimeData* mimeData(const QList items) const Q_DECL_OVERRIDE; bool dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction action) Q_DECL_OVERRIDE; Qt::DropActions supportedDropActions() const Q_DECL_OVERRIDE { //qCDebug(KMIX_LOG) << "supportedDropActions!"; return Qt::MoveAction; } QStringList mimeTypes() const Q_DECL_OVERRIDE { //qCDebug(KMIX_LOG) << "mimeTypes!"; return QStringList() << "application/x-kde-action-list"; } // Skip internal dnd handling in QListWidget ---- how is one supposed to figure this out // without reading the QListWidget code !? void dropEvent(QDropEvent* ev) Q_DECL_OVERRIDE { QAbstractItemView::dropEvent(ev); } private: bool m_activeList; }; class DialogViewConfiguration : public DialogBase { Q_OBJECT public: DialogViewConfiguration(QWidget* parent, ViewBase& view); virtual ~DialogViewConfiguration() = default; public slots: void apply(); private slots: void slotDropped(DialogViewConfigurationWidget *list, int index, DialogViewConfigurationItem *item); void moveSelectionToActiveList(); void moveSelectionToInactiveList(); void selectionChangedActive(); void selectionChangedInactive(); private: //void dragEnterEvent(QDragEnterEvent *event); void prepareControls(QAbstractItemModel* model, bool isActiveView, const GUIProfile::ControlSet &oldCtlSet, GUIProfile::ControlSet &newCtlSet); void createPage(); void addSpacer(int row, int col); void moveSelection(DialogViewConfigurationWidget* from, DialogViewConfigurationWidget* to); ViewBase& _view; QGridLayout *_glayout; QPushButton* moveLeftButton; QPushButton* moveRightButton; DialogViewConfigurationWidget *_qlwActive; DialogViewConfigurationWidget *_qlwInactive; }; #endif diff --git a/gui/kmixtoolbox.cpp b/gui/kmixtoolbox.cpp index e1178fa9..8d00168b 100644 --- a/gui/kmixtoolbox.cpp +++ b/gui/kmixtoolbox.cpp @@ -1,91 +1,90 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2004 Christian Esken * * 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 "gui/kmixtoolbox.h" #include #include #include #include -#include #include #include "gui/guiprofile.h" #include "mdwslider.h" #include "gui/mixdevicewidget.h" #include "core/mixdevice.h" #include "core/mixer.h" #include "viewbase.h" // TODO KMixToolbox is rather superfluous today, as there is no "KMix Applet" any more, and it was probably always bad style. // I only have to think what to do with KMixToolBox::notification() /*********************************************************************************** KMixToolbox contains several GUI relevant methods that are shared between the KMix Main Program, and the KMix Applet. kmixctrl - as not non-GUI application - does NOT link to KMixToolBox. This means: Shared GUI stuff goes into the KMixToolBox class , non-GUI stuff goes into the MixerToolBox class. ***********************************************************************************/ void KMixToolBox::setIcons(QList &mdws, bool on ) { for (int i=0; i < mdws.count(); ++i ){ QWidget *mdw = mdws[i]; if ( mdw->inherits("MixDeviceWidget") ) { // -<- play safe here static_cast(mdw)->setIcons( on ); } } } void KMixToolBox::setLabels(QList &mdws, bool on ) { for (int i=0; i < mdws.count(); ++i ){ QWidget *mdw = mdws[i]; if ( mdw->inherits("MixDeviceWidget") ) { // -<- play safe here static_cast(mdw)->setLabeled( on ); } } } void KMixToolBox::setTicks(QList &mdws, bool on ) { for (int i=0; i < mdws.count(); ++i ){ QWidget *mdw = mdws[i]; if ( mdw->inherits("MixDeviceWidget") ) { // -<- play safe here static_cast(mdw)->setTicks( on ); } } } void KMixToolBox::notification(const char *notificationName, const QString &text, const QStringList &actions, QObject *receiver, const char *actionSlot) { KNotification *notification = new KNotification(notificationName); //notification->setComponentData(componentData()); notification->setText(text); //notification->setPixmap(...); notification->addContext(QLatin1String("Application"), QCoreApplication::applicationName()); if (!actions.isEmpty() && receiver && actionSlot) { notification->setActions(actions); QObject::connect(notification, SIGNAL(activated(uint)), receiver, actionSlot); } notification->sendEvent(); } diff --git a/gui/mdwslider.cpp b/gui/mdwslider.cpp index 6ed852ca..6d9a452b 100644 --- a/gui/mdwslider.cpp +++ b/gui/mdwslider.cpp @@ -1,1252 +1,1250 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2007 Christian Esken * * 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 "gui/mdwslider.h" #include -#include #include #include #include #include #include #include #include #include #include #include #include "core/ControlManager.h" #include "core/mixer.h" #include "gui/guiprofile.h" #include "gui/volumeslider.h" #include "gui/viewbase.h" #include "gui/ksmallslider.h" #include "gui/verticaltext.h" #include "gui/toggletoolbutton.h" bool MDWSlider::debugMe = false; /** * MixDeviceWidget that represents a single mix device, including PopUp, muteLED, ... * * Used in KMix main window and DockWidget and PanelApplet. * It can be configured to include or exclude the captureLED and the muteLED. * The direction (horizontal, vertical) can be configured and whether it should * be "small" (uses KSmallSlider instead of a normal slider widget). * * Due to the many options, this is the most complicated MixDeviceWidget subclass. */ MDWSlider::MDWSlider(shared_ptr md, MixDeviceWidget::MDWFlags flags, ViewBase *view, ProfControl *pctl) : MixDeviceWidget(md, flags, view, pctl), m_linked(true), m_controlGrid(nullptr), m_controlIcon(nullptr), m_controlLabel(nullptr), m_muteButton(nullptr), m_captureButton(nullptr), m_mediaPlayButton(nullptr), m_controlButtonSize(QSize()), m_moveMenu(nullptr), m_sliderInWork(false), m_waitForSoundSetComplete(0) { //qCDebug(KMIX_LOG) << "for" << mixDevice()->readableName() << "flags" << MixDeviceWidget::flags(); createActions(); createWidgets(); createShortcutActions(); // Yes, this looks odd - monitor all events sent to myself by myself? // But it's so that wheel events over the MDWSlider background can be // handled by eventFilter() in the same way as wheel events over child // widgets. Each child widget apart from the sliders themselves also // also needs to have the event filter installed on it, because QWidget // by default ignores the wheel event and does not propagate it. installEventFilter(this); update(); } MDWSlider::~MDWSlider() { qDeleteAll(m_slidersPlayback); qDeleteAll(m_slidersCapture); } void MDWSlider::createActions() { // create actions (on _mdwActions, see MixDeviceWidget) KToggleAction *taction = _mdwActions->add( "stereo" ); taction->setText( i18n("Split Channels") ); connect( taction, SIGNAL(triggered(bool)), SLOT(toggleStereoLinked()) ); // QAction *action; // if ( ! mixDevice()->mixer()->isDynamic() ) { // action = _mdwActions->add( "hide" ); // action->setText( i18n("&Hide") ); // connect( action, SIGNAL(triggered(bool)), SLOT(setDisabled(bool)) ); // } if( mixDevice()->hasMuteSwitch() ) { taction = _mdwActions->add( "mute" ); taction->setText( i18n("Mute") ); connect( taction, SIGNAL(toggled(bool)), SLOT(toggleMuted()) ); } if( mixDevice()->captureVolume().hasSwitch() ) { taction = _mdwActions->add( "recsrc" ); taction->setText( i18n("Capture") ); connect( taction, SIGNAL(toggled(bool)), SLOT(toggleRecsrc()) ); } if( mixDevice()->isMovable() ) { m_moveMenu = new QMenu( i18n("Use Device"), this); connect( m_moveMenu, SIGNAL(aboutToShow()), SLOT(showMoveMenu()) ); } QAction* qaction = _mdwActions->addAction( "keys" ); qaction->setText( i18n("Channel Shortcuts...") ); connect( qaction, SIGNAL(triggered(bool)), SLOT(defineKeys()) ); } void MDWSlider::addGlobalShortcut(QAction* qaction, const QString& label, bool dynamicControl) { QString finalLabel(label); finalLabel += " - " + mixDevice()->readableName() + ", " + mixDevice()->mixer()->readableName(); qaction->setText(label); if (!dynamicControl) { // virtual / dynamic controls won't get shortcuts // #ifdef __GNUC__ // #warning GLOBAL SHORTCUTS ARE NOW ASSIGNED TO ALL CONTROLS, as enableGlobalShortcut(), has not been committed // #endif // b->enableGlobalShortcut(); // enableGlobalShortcut() is not there => use workaround KGlobalAccel::setGlobalShortcut(qaction, QKeySequence()); } } void MDWSlider::createShortcutActions() { bool dynamicControl = mixDevice()->mixer()->isDynamic(); // The following actions are for the "Configure Shortcuts" dialog /* PLEASE NOTE THAT global shortcuts are saved with the name as set with setName(), instead of their action name. This is a bug according to the thread "Global shortcuts are saved with their text-name and not their action-name - Bug?" on kcd. I work around this by using a text with setText() that is unique, but still readable to the user. */ QString actionSuffix = QString(" - %1, %2").arg( mixDevice()->readableName(), mixDevice()->mixer()->readableName() ); QAction *bi, *bd, *bm; // -1- INCREASE VOLUME SHORTCUT ----------------------------------------- bi = _mdwPopupActions->addAction( QString("Increase volume %1").arg( actionSuffix ) ); QString increaseVolumeName = i18n( "Increase Volume" ); addGlobalShortcut(bi, increaseVolumeName, dynamicControl); if ( ! dynamicControl ) connect( bi, SIGNAL(triggered(bool)), SLOT(increaseVolume()) ); // -2- DECREASE VOLUME SHORTCUT ----------------------------------------- bd = _mdwPopupActions->addAction( QString("Decrease volume %1").arg( actionSuffix ) ); QString decreaseVolumeName = i18n( "Decrease Volume" ); addGlobalShortcut(bd, decreaseVolumeName, dynamicControl); if ( ! dynamicControl ) connect(bd, SIGNAL(triggered(bool)), SLOT(decreaseVolume())); // -3- MUTE VOLUME SHORTCUT ----------------------------------------- bm = _mdwPopupActions->addAction( QString("Toggle mute %1").arg( actionSuffix ) ); QString muteVolumeName = i18n( "Toggle Mute" ); addGlobalShortcut(bm, muteVolumeName, dynamicControl); if ( ! dynamicControl ) connect( bm, SIGNAL(triggered(bool)), SLOT(toggleMuted()) ); } QSizePolicy MDWSlider::sizePolicy() const { if (orientation()==Qt::Vertical) { return QSizePolicy( QSizePolicy::Preferred, QSizePolicy::MinimumExpanding ); } else { return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); } } QSize MDWSlider::sizeHint() const { return QSize( 90, QWidget::sizeHint().height()); } /** * This method is a helper for users of this class who would like * to show multiple MDWSlider, and align the sliders. * It returns the "height" (if vertical) of this slider's label. * Warning: Line wraps are computed for a fixed size (100), this may be inaccurate in case, * the widgets have different sizes. */ int MDWSlider::labelExtentHint() const { if (m_controlLabel==nullptr) return (0); if (orientation()==Qt::Vertical) return (m_controlLabel->heightForWidth(m_controlLabel->minimumWidth())); else return (m_controlLabel->sizeHint().width()); } /** * If a label from another widget has more lines than this widget, then a spacer is added under the label */ void MDWSlider::setLabelExtent(int extent) { if (m_controlGrid==nullptr) return; if (orientation()==Qt::Vertical) m_controlGrid->setRowMinimumHeight(1, extent); else m_controlGrid->setColumnMinimumWidth(1, extent); } /** * Alignment helper */ bool MDWSlider::hasMuteButton() const { return (m_muteButton!=nullptr); } /** * See "hasMuteButton" */ bool MDWSlider::hasCaptureLED() const { return (m_captureButton!=nullptr); } void MDWSlider::guiAddCaptureButton(const QString &captureTooltipText) { m_captureButton = new ToggleToolButton("media-record", this); m_captureButton->setSmallSize(flags() & MixDeviceWidget::SmallSize); m_captureButton->installEventFilter(this); connect(m_captureButton, SIGNAL(clicked(bool)), this, SLOT(toggleRecsrc())); m_captureButton->setToolTip(captureTooltipText); } void MDWSlider::guiAddMuteButton(const QString &muteTooltipText) { m_muteButton = new ToggleToolButton("audio-volume-high", this); m_muteButton->setInactiveIcon("audio-volume-muted"); m_muteButton->setSmallSize(flags() & MixDeviceWidget::SmallSize); m_muteButton->installEventFilter(this); connect(m_muteButton, SIGNAL(clicked(bool)), this, SLOT(toggleMuted())); m_muteButton->setToolTip(muteTooltipText); } void MDWSlider::guiAddControlLabel(Qt::Alignment alignment, const QString &channelName) { m_controlLabel = new QLabel(channelName, this); m_controlLabel->setWordWrap(true); m_controlLabel->setAlignment(alignment); m_controlLabel->installEventFilter(this); } void MDWSlider::guiAddControlIcon(const QString &tooltipText) { m_controlIcon = new QLabel(this); ToggleToolButton::setIndicatorIcon(mixDevice()->iconName(), m_controlIcon, (flags() & MixDeviceWidget::SmallSize)); m_controlIcon->setToolTip(tooltipText); m_controlIcon->installEventFilter(this); } QWidget *MDWSlider::guiAddButtonSpacer() { if (hasMuteButton() || hasCaptureLED()) return (nullptr); // spacer not needed QWidget *buttonSpacer = new QWidget(this); if (orientation()==Qt::Vertical) // vertical sliders { buttonSpacer->setMinimumHeight(controlButtonSize().height()); buttonSpacer->setMaximumWidth(1); } else // horizontal sliders { buttonSpacer->setMinimumWidth(controlButtonSize().width()); buttonSpacer->setMaximumHeight(1); } buttonSpacer->installEventFilter(this); return (buttonSpacer); } QSize MDWSlider::controlButtonSize() { if (!m_controlButtonSize.isValid()) // not calculated yet { auto *buttonSpacer = new QToolButton(); ToggleToolButton::setIndicatorIcon("unknown", buttonSpacer, (flags() & MixDeviceWidget::SmallSize)); m_controlButtonSize = buttonSpacer->sizeHint(); qCDebug(KMIX_LOG) << m_controlButtonSize; delete buttonSpacer; } return (m_controlButtonSize); } /** * Creates all widgets : Icon, Label, Mute-Button, Slider(s) and Capture-Button. */ void MDWSlider::createWidgets() { const bool includePlayback = profileControl()->useSubcontrolPlayback(); const bool includeCapture = profileControl()->useSubcontrolCapture(); const bool wantsPlaybackSliders = includePlayback && (mixDevice()->playbackVolume().count()>0); const bool wantsCaptureSliders = includeCapture && (mixDevice()->captureVolume().count()>0); const bool wantsCaptureLED = includeCapture && (flags() & MixDeviceWidget::ShowCapture); const bool wantsMuteButton = includePlayback && (flags() & MixDeviceWidget::ShowMute); const MediaController *mediaController = mixDevice()->mediaController(); const bool wantsMediaControls = mediaController->hasControls(); const QString channelName = mixDevice()->readableName(); QString tooltipText = channelName; QString captureTooltipText = i18nc("%1=channel", "Capture/Uncapture %1", channelName); QString muteTooltipText = i18nc("%1=channel", "Mute/Unmute %1", channelName); if (flags() & MixDeviceWidget::ShowMixerName) { const QString mixerName = mixDevice()->mixer()->readableName(); tooltipText = i18nc("%1=device %2=channel", "%1\n%2", mixerName, tooltipText); captureTooltipText = i18nc("%1=device %2=channel", "%1\n%2", mixerName, captureTooltipText); muteTooltipText = i18nc("%1=device %2=channel", "%1\n%2", mixerName, muteTooltipText); } m_controlGrid = new QGridLayout(this); setLayout(m_controlGrid); QBoxLayout *volLayout; if (orientation()==Qt::Vertical) // vertical sliders { m_controlGrid->setContentsMargins(2, 0, 2, 0); const Qt::Alignment sliderAlign = Qt::AlignHCenter|Qt::AlignBottom; // Row 0: Control type icon guiAddControlIcon(tooltipText); m_controlGrid->addWidget(m_controlIcon, 0, 0, 1, -1, Qt::AlignHCenter|Qt::AlignTop); // Row 1: Device name label guiAddControlLabel(Qt::AlignHCenter, channelName); m_controlGrid->addWidget(m_controlLabel, 1, 0, 1, -1, Qt::AlignHCenter|Qt::AlignTop); // Row 2: Sliders int col = 0; // current column being filled int playbackCol = 0; // where these sliders ended up int captureCol = 1; // or default button column if none if (wantsPlaybackSliders) { volLayout = new QHBoxLayout(); volLayout->setAlignment(sliderAlign); addSliders(volLayout, 'p', mixDevice()->playbackVolume(), m_slidersPlayback, tooltipText); m_controlGrid->addLayout(volLayout, 2, col); playbackCol = col; ++col; } if (wantsCaptureSliders) { volLayout = new QHBoxLayout(); volLayout->setAlignment(sliderAlign); addSliders(volLayout, 'c', mixDevice()->captureVolume(), m_slidersCapture, tooltipText); m_controlGrid->addLayout(volLayout, 2, col); captureCol = col; ++col; } if (wantsMediaControls) { volLayout = new QHBoxLayout(); volLayout->setAlignment(sliderAlign); addMediaControls(volLayout); m_controlGrid->addLayout(volLayout, 2, col); } m_controlGrid->setRowStretch(2, 1); // sliders need the most space // Row 3: Control buttons if (wantsMuteButton && mixDevice()->hasMuteSwitch()) { guiAddMuteButton(muteTooltipText); m_controlGrid->addWidget(m_muteButton, 3, playbackCol, Qt::AlignHCenter|Qt::AlignTop); } if (wantsCaptureLED && mixDevice()->captureVolume().hasSwitch()) { guiAddCaptureButton(captureTooltipText); m_controlGrid->addWidget(m_captureButton, 3, captureCol, Qt::AlignHCenter|Qt::AlignTop); } // If nether a mute nor a capture button is present, then put a // dummy spacer button (in column 0, where the mute button would // normally go). This is to maintain the size of the slider // relative to others that do have one or both buttons. // // We have to do this, rather than setting a minimum height for row 3, // as in the case where it is needed row 3 will be empty and QGridLayout // ignores the minimum height set on it. QWidget *buttonSpacer = guiAddButtonSpacer(); if (buttonSpacer!=nullptr) m_controlGrid->addWidget(buttonSpacer, 3, 0); } else // horizontal sliders { const Qt::Alignment sliderAlign = Qt::AlignHCenter|Qt::AlignVCenter; // Column 0: Control type icon guiAddControlIcon(tooltipText); m_controlGrid->addWidget(m_controlIcon, 0, 0, -1, 1, Qt::AlignLeft|Qt::AlignVCenter); // Column 1: Device name label guiAddControlLabel(Qt::AlignLeft, channelName); m_controlGrid->addWidget(m_controlLabel, 0, 1, -1, 1, Qt::AlignLeft|Qt::AlignVCenter); // Column 2: Sliders int row = 0; // current row being filled int playbackRow = 0; // where these sliders ended up int captureRow = 1; // or default button row if none if (wantsPlaybackSliders) { volLayout = new QVBoxLayout(); volLayout->setAlignment(sliderAlign); addSliders(volLayout, 'p', mixDevice()->playbackVolume(), m_slidersPlayback, tooltipText); m_controlGrid->addLayout(volLayout, row, 2); playbackRow = row; ++row; } if (wantsCaptureSliders) { volLayout = new QVBoxLayout(); volLayout->setAlignment(sliderAlign); addSliders(volLayout, 'c', mixDevice()->captureVolume(), m_slidersCapture, tooltipText); m_controlGrid->addLayout(volLayout, row, 2); captureRow = row; ++row; } if (wantsMediaControls) { volLayout = new QVBoxLayout(); volLayout->setAlignment(sliderAlign); addMediaControls(volLayout); m_controlGrid->addLayout(volLayout, row, 2); } m_controlGrid->setColumnStretch(2, 1); // sliders need the most space // Column 3: Control buttons if (wantsMuteButton && mixDevice()->hasMuteSwitch()) { guiAddMuteButton(muteTooltipText); m_controlGrid->addWidget(m_muteButton, playbackRow, 3, Qt::AlignRight|Qt::AlignVCenter); } if (wantsCaptureLED && mixDevice()->captureVolume().hasSwitch()) { guiAddCaptureButton(captureTooltipText); m_controlGrid->addWidget(m_captureButton, captureRow, 3, Qt::AlignRight|Qt::AlignVCenter); } // Dummy spacer button QWidget *buttonSpacer = guiAddButtonSpacer(); if (buttonSpacer!=nullptr) m_controlGrid->addWidget(buttonSpacer, 0, 3); } const bool stereoLinked = !profileControl()->isSplit(); setStereoLinked( stereoLinked ); // Activate it explicitly in KDE3 because of PanelApplet/Kicker issues. // Not sure whether this is necessary 2 generations later. layout()->activate(); } QString MDWSlider::calculatePlaybackIcon(MediaController::PlayState playState) { QString mediaIconName; switch (playState) { case MediaController::PlayPlaying: // playing => show pause icon mediaIconName = "media-playback-pause"; break; case MediaController::PlayPaused: // stopped/paused => show play icon mediaIconName = "media-playback-start"; break; case MediaController::PlayStopped: // stopped/paused => show play icon mediaIconName = "media-playback-start"; break; default: // unknown => not good, probably result from player has not yet arrived => show a play button mediaIconName = "media-playback-start"; break; } return mediaIconName; } void MDWSlider::addMediaControls(QBoxLayout* volLayout) { MediaController *mediaController = mixDevice()->mediaController(); QBoxLayout *mediaLayout; if (orientation()==Qt::Vertical) mediaLayout = new QVBoxLayout(); else mediaLayout = new QHBoxLayout(); // QFrame* frame1 = new QFrame(this); // frame1->setFrameShape(QFrame::StyledPanel); QWidget* frame = this; // or frame1 mediaLayout->addStretch(); if (mediaController->hasMediaPrevControl()) { QToolButton *lbl = addMediaButton("media-skip-backward", mediaLayout, frame); connect(lbl, SIGNAL(clicked(bool)), this, SLOT(mediaPrev(bool))); } if (mediaController->hasMediaPlayControl()) { MediaController::PlayState playState = mediaController->getPlayState(); QString mediaIcon = calculatePlaybackIcon(playState); m_mediaPlayButton = addMediaButton(mediaIcon, mediaLayout, frame); connect(m_mediaPlayButton, SIGNAL(clicked(bool)), this, SLOT(mediaPlay(bool))); } if (mediaController->hasMediaNextControl()) { QToolButton *lbl = addMediaButton("media-skip-forward", mediaLayout, frame); connect(lbl, SIGNAL(clicked(bool)), this, SLOT(mediaNext(bool))); } mediaLayout->addStretch(); volLayout->addLayout(mediaLayout); } QToolButton* MDWSlider::addMediaButton(QString iconName, QLayout* layout, QWidget *parent) { QToolButton *lbl = new QToolButton(parent); - lbl->setIconSize(QSize(IconSize(KIconLoader::Toolbar), IconSize(KIconLoader::Toolbar))); lbl->setAutoRaise(true); lbl->setCheckable(false); ToggleToolButton::setIndicatorIcon(iconName, lbl); layout->addWidget(lbl); return lbl; } /** * Updates the icon according to the data model. */ void MDWSlider::updateMediaButton() { if (m_mediaPlayButton == 0) return; // has no media button MediaController *mediaController = mixDevice()->mediaController(); QString mediaIconName = calculatePlaybackIcon(mediaController->getPlayState()); ToggleToolButton::setIndicatorIcon(mediaIconName, m_mediaPlayButton); } void MDWSlider::mediaPrev(bool) { mixDevice()->mediaPrev(); } void MDWSlider::mediaNext(bool) { mixDevice()->mediaNext(); } void MDWSlider::mediaPlay(bool) { mixDevice()->mediaPlay(); } static QWidget *createLabel(QWidget *parent, const QString &label, Qt::Orientation orient, bool small) { QFont qf; qf.setPointSize(8); QWidget *labelWidget; if (orient == Qt::Horizontal) { auto *ql = new QLabel(label, parent); if (small) ql->setFont(qf); labelWidget = ql; } else { auto *vt = new VerticalText(parent, label); if (small) vt->setFont(qf); labelWidget = vt; } return (labelWidget); } void MDWSlider::addSliders( QBoxLayout *volLayout, char type, Volume& vol, QList& ref_sliders, QString tooltipText) { const int minSliderSize = fontMetrics().height() * 10; long minvol = vol.minVolume(); long maxvol = vol.maxVolume(); QMap vols = vol.getVolumes(); foreach (VolumeChannel vc, vols ) { //qCDebug(KMIX_LOG) << "Add label to " << vc.chid << ": " << Volume::channelNameReadable(vc.chid); QWidget *subcontrolLabel; QString subcontrolTranslation; if ( type == 'c' ) subcontrolTranslation += i18n("Capture") + ' '; subcontrolTranslation += Volume::channelNameReadable(vc.chid); subcontrolLabel = createLabel(this, subcontrolTranslation, orientation(), true); volLayout->addWidget(subcontrolLabel); QAbstractSlider* slider; if (flags() & MixDeviceWidget::SmallSize) { slider = new KSmallSlider( minvol, maxvol, (maxvol-minvol+1) / Volume::VOLUME_PAGESTEP_DIVISOR, vol.getVolume( vc.chid ), orientation(), this ); } // small else { slider = new VolumeSlider(orientation(), this); slider->setMinimum(minvol); slider->setMaximum(maxvol); slider->setPageStep(maxvol / Volume::VOLUME_PAGESTEP_DIVISOR); slider->setValue( vol.getVolume( vc.chid ) ); volumeValues.push_back( vol.getVolume( vc.chid ) ); extraData(slider).setSubcontrolLabel(subcontrolLabel); if (orientation()==Qt::Vertical) slider->setMinimumHeight(minSliderSize); else slider->setMinimumWidth(minSliderSize); if ( !profileControl()->getBackgroundColor().isEmpty() ) { slider->setStyleSheet("QSlider { background-color: " + profileControl()->getBackgroundColor() + " }"); } } // not small extraData(slider).setChid(vc.chid); // slider->installEventFilter( this ); if ( type == 'p' ) { slider->setToolTip( tooltipText ); } else { QString captureTip( i18n( "%1 (capture)", tooltipText ) ); slider->setToolTip( captureTip ); } volLayout->addWidget( slider ); // add to layout ref_sliders.append ( slider ); // add to list //ref_slidersChids.append(vc.chid); connect( slider, SIGNAL(valueChanged(int)), SLOT(volumeChange(int)) ); connect( slider, SIGNAL(sliderPressed()), SLOT(sliderPressed()) ); connect( slider, SIGNAL(sliderReleased()), SLOT(sliderReleased()) ); } // for all channels of this device } /** * Return the VolumeSliderExtraData from either VolumeSlider or KSmallSlider. * You MUST extend this method, should you decide to add more Slider Widget classes. * * @param slider * @return */ VolumeSliderExtraData& MDWSlider::extraData(QAbstractSlider *slider) { VolumeSlider* sl = qobject_cast(slider); if ( sl ) return sl->extraData; KSmallSlider* sl2 = qobject_cast(slider); return sl2->extraData; } void MDWSlider::sliderPressed() { m_sliderInWork = true; } void MDWSlider::sliderReleased() { m_sliderInWork = false; } QString MDWSlider::iconName() { return mixDevice()->iconName(); } void MDWSlider::toggleStereoLinked() { setStereoLinked( !isStereoLinked() ); } void MDWSlider::setStereoLinked(bool value) { m_linked = value; int overallSlidersToShow = 0; if ( ! m_slidersPlayback.isEmpty() ) overallSlidersToShow += ( m_linked ? 1 : m_slidersPlayback.count() ); if ( ! m_slidersCapture.isEmpty() ) overallSlidersToShow += ( m_linked ? 1 : m_slidersCapture.count() ); bool showSubcontrolLabels = (overallSlidersToShow >= 2); setStereoLinkedInternal(m_slidersPlayback, showSubcontrolLabels); setStereoLinkedInternal(m_slidersCapture , showSubcontrolLabels); update(); // Call update(), so that the sliders can adjust EITHER to the individual values OR the average value. } void MDWSlider::setStereoLinkedInternal(QList& ref_sliders, bool showSubcontrolLabels) { if ( ref_sliders.isEmpty()) return; bool first = true; foreach ( QAbstractSlider* slider1, ref_sliders ) { slider1->setVisible(!m_linked || first); // One slider (the 1st) is always shown extraData(slider1).getSubcontrolLabel()->setVisible(!m_linked && showSubcontrolLabels); // (*) first = false; /* (*) cesken: I have excluded the "|| first" check because the text would not be nice: * It would be "Left" or "Capture Left", while it should be "Playback" and "Capture" in the "linked" case. * * But the only affected situation is when we have playback AND capture on the same control, where we show no label. * It would be nice to put at least a "Capture" label on the capture subcontrol instead. * To achieve this we would need to exchange the Text on the first capture subcontrol dynamically. This can * be done, but I'll leave this open for now. */ } // Redo the tickmarks to last slider in the slider list. // The implementation is not obvious, so lets explain: // We ALWAYS have tickmarks on the LAST slider. Sometimes the slider is not shown, and then we just don't bother. // a) So, if the last slider has tickmarks, we can always call setTicks( true ). // b) if the last slider has NO tickmarks, there ae no tickmarks at all, and we don't need to redo the tickmarks. QSlider* slider = qobject_cast( ref_sliders.last() ); if( slider && slider->tickPosition() != QSlider::NoTicks) setTicks( true ); } void MDWSlider::setLabeled(bool value) { if ( m_controlLabel != 0) m_controlLabel->setVisible(value); layout()->activate(); } void MDWSlider::setTicks( bool value ) { if (m_slidersPlayback.count() != 0) setTicksInternal(m_slidersPlayback, value); if (m_slidersCapture.count() != 0) setTicksInternal(m_slidersCapture, value); } /** * Enables or disables tickmarks * Please note that always only the first and last slider have tickmarks. */ void MDWSlider::setTicksInternal(QList& ref_sliders, bool ticks) { VolumeSlider* slider = qobject_cast( ref_sliders[0]); if (slider == 0 ) return; // Ticks are only in VolumeSlider, but not in KSmallslider if( ticks ) { if( isStereoLinked() ) slider->setTickPosition( QSlider::TicksRight ); else { slider->setTickPosition( QSlider::NoTicks ); slider = qobject_cast(ref_sliders.last()); slider->setTickPosition( QSlider::TicksLeft ); } } else { slider->setTickPosition( QSlider::NoTicks ); slider = qobject_cast(ref_sliders.last()); slider->setTickPosition( QSlider::NoTicks ); } } void MDWSlider::setIcons(bool value) { if ( m_controlIcon != 0 ) { if ( ( !m_controlIcon->isHidden() ) !=value ) { if (value) m_controlIcon->show(); else m_controlIcon->hide(); layout()->activate(); } } // if it has an icon } void MDWSlider::setColors( QColor high, QColor low, QColor back ) { for( int i=0; i(slider); if ( smallSlider ) smallSlider->setColors( high, low, back ); } for( int i=0; i(slider); if ( smallSlider ) smallSlider->setColors( high, low, back ); } } void MDWSlider::setMutedColors( QColor high, QColor low, QColor back ) { for( int i=0; i(slider); if ( smallSlider ) smallSlider->setGrayColors( high, low, back ); } for( int i=0; i(slider); if ( smallSlider ) smallSlider->setGrayColors( high, low, back ); } } /** This slot is called, when a user has changed the volume via the KMix Slider. */ void MDWSlider::volumeChange( int ) { // if ( mixDevice()->id() == "Headphone:0" ) // { // qCDebug(KMIX_LOG) << "headphone bug"; // } if (!m_slidersPlayback.isEmpty()) { ++m_waitForSoundSetComplete; volumeValues.push_back(m_slidersPlayback.first()->value()); volumeChangeInternal(mixDevice()->playbackVolume(), m_slidersPlayback); } if (!m_slidersCapture.isEmpty()) { volumeChangeInternal(mixDevice()->captureVolume(), m_slidersCapture); } QSignalBlocker blocker(view()); mixDevice()->mixer()->commitVolumeChange(mixDevice()); } void MDWSlider::volumeChangeInternal(Volume& vol, QList& ref_sliders) { if (isStereoLinked()) { QAbstractSlider* firstSlider = ref_sliders.first(); mixDevice()->setMuted(false); vol.setAllVolumes(firstSlider->value()); } else { for (int i = 0; i < ref_sliders.count(); i++) { if (mixDevice()->isMuted()) { // changing from muted state: unmute (the "if" above is actually superfluous) mixDevice()->setMuted(false); } QAbstractSlider *sliderWidget = ref_sliders[i]; vol.setVolume(extraData(sliderWidget).getChid(), sliderWidget->value()); } // iterate over all sliders } } /** This slot is called, when a user has clicked the recsrc button. Also it is called by any other associated QAction like the context menu. */ void MDWSlider::toggleRecsrc() { setRecsrc( !mixDevice()->isRecSource() ); } void MDWSlider::setRecsrc(bool value) { if ( mixDevice()->captureVolume().hasSwitch() ) { mixDevice()->setRecSource( value ); mixDevice()->mixer()->commitVolumeChange( mixDevice() ); } } /** This slot is called, when a user has clicked the mute button. Also it is called by any other associated QAction like the context menu. */ void MDWSlider::toggleMuted() { setMuted( !mixDevice()->isMuted() ); } void MDWSlider::setMuted(bool value) { if ( mixDevice()->hasMuteSwitch() ) { mixDevice()->setMuted( value ); mixDevice()->mixer()->commitVolumeChange(mixDevice()); } } void MDWSlider::setDisabled( bool hide ) { emit guiVisibilityChange(this, !hide); } /** * This slot is called on a Keyboard Shortcut event, except for the XF86Audio* shortcuts which are handled by the * KMixWindow class. So for 99.9% of all users, this method is never called. */ void MDWSlider::increaseVolume() { increaseOrDecreaseVolume(false, Volume::Both); } /** * This slot is called on a Keyboard Shortcut event, except for the XF86Audio* shortcuts which hare handled by the * KMixWindow class. So for 99.9% of all users, this method is never called. */ void MDWSlider::decreaseVolume() { increaseOrDecreaseVolume(true, Volume::Both); } /** * Increase or decrease all playback and capture channels of the given control. * This method is very similar to Mixer::increaseOrDecreaseVolume(), but it will * auto-unmute on increase. * * @param mixdeviceID The control name * @param decrease true for decrease. false for increase */ void MDWSlider::increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType) { mixDevice()->increaseOrDecreaseVolume(decrease, volumeType); // I should possibly not block, as the changes that come back from the Soundcard // will be ignored (e.g. because of capture groups) // qCDebug(KMIX_LOG) << "MDWSlider is blocking signals for " << view()->id(); // bool oldViewBlockSignalState = view()->blockSignals(true); mixDevice()->mixer()->commitVolumeChange(mixDevice()); // qCDebug(KMIX_LOG) << "MDWSlider is unblocking signals for " << view()->id(); // view()->blockSignals(oldViewBlockSignalState); } /** * Must be called by the triggered(bool) signal from a QAction. */ void MDWSlider::moveStream(bool checked) { Q_UNUSED(checked); QAction *act = qobject_cast(sender()); Q_ASSERT(act!=nullptr); const QString destId = act->data().toString(); mixDevice()->mixer()->moveStream(mixDevice()->id(), destId); } /** * This is called whenever there are volume updates pending from the hardware for this MDW. */ void MDWSlider::update() { // bool debugMe = (mixDevice()->id() == "PCM:0" ); // if (debugMe) qCDebug(KMIX_LOG) << "The update() PCM:0 playback state" << mixDevice()->isMuted() // << ", vol=" << mixDevice()->playbackVolume().getAvgVolumePercent(Volume::MALL); if ( m_slidersPlayback.count() != 0 || mixDevice()->hasMuteSwitch() ) updateInternal(mixDevice()->playbackVolume(), m_slidersPlayback, mixDevice()->isMuted() ); if ( m_slidersCapture.count() != 0 || mixDevice()->captureVolume().hasSwitch() ) updateInternal(mixDevice()->captureVolume(), m_slidersCapture, mixDevice()->isNotRecSource() ); if (m_controlLabel!=nullptr) { QLabel *l; VerticalText *v; if ((l = dynamic_cast(m_controlLabel))) l->setText(mixDevice()->readableName()); else if ((v = dynamic_cast(m_controlLabel))) v->setText(mixDevice()->readableName()); } updateAccesability(); } /** * * @param vol * @param ref_sliders * @param muted Future directions: passing "muted" should not be necessary any longer - due to getVolumeForGUI() */ void MDWSlider::updateInternal(Volume& vol, QList& ref_sliders, bool muted) { // bool debugMe = (mixDevice()->id() == "PCM:0" ); // if (debugMe) // { // qCDebug(KMIX_LOG) << "The updateInternal() PCM:0 playback state" << mixDevice()->isMuted() // << ", vol=" << mixDevice()->playbackVolume().getAvgVolumePercent(Volume::MALL); // } for (int i = 0; i-1 && --m_waitForSoundSetComplete<1) { m_waitForSoundSetComplete = 0; volumeValues.removeAt(volume_index); if (!m_sliderInWork) slider->setValue(useVolume); } else if (!m_sliderInWork && m_waitForSoundSetComplete<1) { slider->setValue(useVolume); } // --- Avoid feedback loops END ----------------- KSmallSlider *smallSlider = qobject_cast(slider); if (smallSlider!=nullptr) // faster than QObject::inherits() { smallSlider->setGray(mixDevice()->isMuted()); } } // for all sliders // update mute state if (m_muteButton!=nullptr) { QSignalBlocker blocker(m_muteButton); m_muteButton->setActive(!mixDevice()->isMuted()); } // update capture state if (m_captureButton!=nullptr) { QSignalBlocker blocker(m_captureButton); m_captureButton->setActive(mixDevice()->isRecSource()); } } #ifndef QT_NO_ACCESSIBILITY void MDWSlider::updateAccesability() { if (m_linked) { if (!m_slidersPlayback.isEmpty()) m_slidersPlayback[0]->setAccessibleName(m_slidersPlayback[0]->toolTip()); if (!m_slidersCapture.isEmpty()) m_slidersCapture[0]->setAccessibleName(m_slidersCapture[0]->toolTip()); } else { QList vols = mixDevice()->playbackVolume().getVolumes().values(); foreach (QAbstractSlider *slider, m_slidersPlayback) { slider->setAccessibleName(slider->toolTip()+ " (" +Volume::channelNameReadable(vols.first().chid)+')'); vols.pop_front(); } vols = mixDevice()->captureVolume().getVolumes().values(); foreach (QAbstractSlider *slider, m_slidersCapture) { slider->setAccessibleName(slider->toolTip()+ " (" +Volume::channelNameReadable(vols.first().chid)+')'); vols.pop_front(); } } } #endif void MDWSlider::showContextMenu(const QPoint &pos) { if (view()==nullptr) return; QMenu *menu = view()->getPopup(); - menu->addSection( SmallIcon( "kmix" ), mixDevice()->readableName() ); + menu->addSection( QIcon::fromTheme( "kmix" ), mixDevice()->readableName() ); if (m_moveMenu) { MixSet *ms = mixDevice()->moveDestinationMixSet(); Q_ASSERT(ms!=nullptr); m_moveMenu->setEnabled(ms->count()>1); // The "Event Sounds" stream cannot be moved at present. This is because // Mixer_PULSE::moveStream() does not record the stream ID in the // output stream list and hence cannot get its PulseAudio stream index. // I don't know whether this is a design decision or a PA limitation. if (mixDevice()->id().endsWith(":event")) m_moveMenu->setEnabled(false); menu->addMenu( m_moveMenu ); } if ( m_slidersPlayback.count()>1 || m_slidersCapture.count()>1) { KToggleAction *stereo = qobject_cast(_mdwActions->action("stereo")); if (stereo!=nullptr) { QSignalBlocker blocker(stereo); stereo->setChecked(!isStereoLinked()); menu->addAction( stereo ); } } if ( mixDevice()->captureVolume().hasSwitch() ) { KToggleAction *ta = qobject_cast(_mdwActions->action("recsrc")); if (ta!=nullptr) { QSignalBlocker blocker(ta); ta->setChecked( mixDevice()->isRecSource() ); menu->addAction( ta ); } } if ( mixDevice()->hasMuteSwitch() ) { KToggleAction *ta = qobject_cast(_mdwActions->action("mute")); if (ta!=nullptr) { QSignalBlocker blocker(ta); ta->setChecked( mixDevice()->isMuted() ); menu->addAction( ta ); } } // QAction *a = _mdwActions->action( "hide" ); // if ( a ) // menu->addAction( a ); QAction *b = _mdwActions->action( "keys" ); if (b!=nullptr) { menu->addSeparator(); menu->addAction(b); } menu->popup(pos); } void MDWSlider::showMoveMenu() { const MixSet *ms = mixDevice()->moveDestinationMixSet(); Q_ASSERT(ms!=nullptr); const QString cur = mixDevice()->mixer()->currentStreamDevice(mixDevice()->id()); // There is no need to keep a record of the actions (in a KActionCollection // or otherwise); QMenu::clear() will delete them as long as they are owned // by the menu. m_moveMenu->clear(); // Default action QAction *act = new QAction(i18n("Automatic (according to category)"), m_moveMenu); act->setData(QString()); connect(act, &QAction::triggered, this, &MDWSlider::moveStream, Qt::QueuedConnection); m_moveMenu->addAction(act); m_moveMenu->addSeparator(); // Device actions foreach (const shared_ptr md, *ms) { act = new QAction(QIcon::fromTheme(md->iconName()), md->readableName(), m_moveMenu); act->setData(md->id()); act->setCheckable(true); if (md->id()==cur) act->setChecked(true); connect(act, &QAction::triggered, this, &MDWSlider::moveStream, Qt::QueuedConnection); m_moveMenu->addAction(act); } } /** * An event filter for the various widgets making up this control. * * Redirect all wheel events to the main slider, so that they will be * handled consistently regardless of where the pointer actually is. */ bool MDWSlider::eventFilter(QObject *obj, QEvent *ev) { if (ev->type()!=QEvent::Wheel) return (QWidget::eventFilter(obj, ev)); // only want wheel events if (!ev->spontaneous()) return (false); // avoid recursion on slider QAbstractSlider *slider = qobject_cast(obj); if (slider!=nullptr) // event is over a slider { // Do nothing in this case. No event filter is installed // on a slider, and it will handle the wheel event itself. qCWarning(KMIX_LOG) << "unexpected wheel event on slider" << slider; return (false); } // Mouse is not over a slider. Find the principal slider (the first // playback control if there are any, otherwise the first capture // control if any) and redirect the event to that. if (!m_slidersPlayback.isEmpty()) slider = m_slidersPlayback.first(); else if (!m_slidersCapture.isEmpty()) slider = m_slidersCapture.first(); else slider = nullptr; if (slider!=nullptr) { //qCDebug(KMIX_LOG) << "identified for slider" << slider; QCoreApplication::sendEvent(slider, ev); } return (true); // wheel event handled } diff --git a/gui/mdwslider.h b/gui/mdwslider.h index 05886731..be1a2456 100644 --- a/gui/mdwslider.h +++ b/gui/mdwslider.h @@ -1,158 +1,156 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * * Copyright Chrisitan Esken * * 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. */ #ifndef MDWSLIDER_H #define MDWSLIDER_H #include -#include - #include "gui/volumeslider.h" #include "gui/mixdevicewidget.h" #include "core/volume.h" class QBoxLayout; class QGridLayout; class QToolButton; class QLabel; class QMenu; class MixDevice; class VerticalText; class ViewBase; class ToggleToolButton; class MDWSlider : public MixDeviceWidget { Q_OBJECT public: MDWSlider(shared_ptr md, MixDeviceWidget::MDWFlags flags, ViewBase *view, ProfControl *pctl = nullptr); virtual ~MDWSlider(); enum LabelType { LT_ALL, LT_FIRST_CAPTURE, LT_NONE }; void addActionToPopup( QAction *action ); void createActions(); void createShortcutActions(); // GUI bool isStereoLinked() const Q_DECL_OVERRIDE { return m_linked; } void setStereoLinked( bool value ) Q_DECL_OVERRIDE; void setLabeled( bool value ) Q_DECL_OVERRIDE; void setTicks( bool ticks ) Q_DECL_OVERRIDE; void setIcons( bool value ) Q_DECL_OVERRIDE; QToolButton* addMediaButton(QString iconName, QLayout* layout, QWidget *parent); void updateMediaButton(); void setColors( QColor high, QColor low, QColor back ) Q_DECL_OVERRIDE; void setMutedColors( QColor high, QColor low, QColor back ) Q_DECL_OVERRIDE; bool eventFilter(QObject *obj, QEvent *ev) Q_DECL_OVERRIDE; QString iconName(); // Layout QSizePolicy sizePolicy() const; QSize sizeHint() const Q_DECL_OVERRIDE; int labelExtentHint() const Q_DECL_OVERRIDE; void setLabelExtent(int extent) Q_DECL_OVERRIDE; bool hasMuteButton() const; bool hasCaptureLED() const; static bool debugMe; public slots: void toggleRecsrc(); void toggleMuted(); void toggleStereoLinked(); void setDisabled( bool value ) Q_DECL_OVERRIDE; void update() Q_DECL_OVERRIDE; void showMoveMenu(); void showContextMenu( const QPoint &pos = QCursor::pos() ) Q_DECL_OVERRIDE; void increaseOrDecreaseVolume(bool arg1, Volume::VolumeTypeFlag volumeType); VolumeSliderExtraData& extraData(QAbstractSlider *slider); void addMediaControls(QBoxLayout* arg1); private slots: void setRecsrc(bool value); void setMuted(bool value); void volumeChange( int ); void sliderPressed(); void sliderReleased(); void increaseVolume(); void decreaseVolume(); void mediaPlay(bool); void mediaNext(bool); void mediaPrev(bool); void moveStream(bool checked); private: void createWidgets(); void addSliders( QBoxLayout *volLayout, char type, Volume& vol, QList& ref_sliders, QString tooltipText ); // Methods that are called two times from a wrapper. Once for playabck, once for capture void setStereoLinkedInternal( QList< QAbstractSlider* >& ref_sliders, bool showSubcontrolLabels); void setTicksInternal( QList< QAbstractSlider* >& ref_sliders, bool ticks ); void volumeChangeInternal(Volume& vol, QList< QAbstractSlider* >& ref_sliders ); void updateInternal(Volume& vol, QList< QAbstractSlider* >& ref_sliders, bool muted); #ifndef QT_NO_ACCESSIBILITY void updateAccesability(); #endif QString calculatePlaybackIcon(MediaController::PlayState playState); QWidget *guiAddButtonSpacer(); void guiAddCaptureButton(const QString &captureTooltipText); void guiAddMuteButton(const QString &muteTooltipText); void guiAddControlIcon(const QString &tooltipText); void guiAddControlLabel(Qt::Alignment alignment, const QString &channelName); void addGlobalShortcut(QAction* action, const QString& label, bool dynamicControl); QSize controlButtonSize(); bool m_linked; QGridLayout *m_controlGrid; QLabel *m_controlIcon; QLabel *m_controlLabel; // is either QLabel or VerticalText ToggleToolButton *m_muteButton; ToggleToolButton *m_captureButton; QToolButton *m_mediaPlayButton; QSize m_controlButtonSize; QMenu *m_moveMenu; QList m_slidersPlayback; QList m_slidersCapture; bool m_sliderInWork; int m_waitForSoundSetComplete; QList volumeValues; }; #endif diff --git a/gui/mixdevicewidget.cpp b/gui/mixdevicewidget.cpp index 460ecff8..7a76f45c 100644 --- a/gui/mixdevicewidget.cpp +++ b/gui/mixdevicewidget.cpp @@ -1,109 +1,108 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * 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 "gui/mixdevicewidget.h" #include -#include #include #include #include #include #include #include #include #include "core/mixer.h" #include "core/mixertoolbox.h" #include "viewbase.h" #include "ksmallslider.h" #include "verticaltext.h" /** * Base Class for any Widget that represents a MixDevice. * The mix device can be a real (hardware bound) MixDevice or a virtual mix device. * * The direction (horizontal, vertical) can be configured and whether it should * be "small" (uses KSmallSlider instead of a normal slider widget). The actual implementations * SHOULD honor these values - those who do not might not be suitable for placing in * the panel applet or any other smallish settings. */ MixDeviceWidget::MixDeviceWidget(shared_ptr md, MDWFlags flags, ViewBase *view, ProfControl *pctl) : QWidget(view), m_flags(flags), m_mixdevice(md), m_view(view) { setContextMenuPolicy(Qt::DefaultContextMenu); // The control profile. ViewSliders uses the default from the MixDevice. // ViewDockAreaPopup sets a special one. m_pctl = pctl; if (m_pctl==nullptr) m_pctl = md->controlProfile(); Q_ASSERT(m_pctl!=nullptr); _mdwActions = new KActionCollection( this ); _mdwPopupActions = new KActionCollection( this ); m_shortcutsDialog = nullptr; QString name (md->id()); /* char* whatsThisChar = whatsthis.toUtf8().data(); QString w; w = ki18n(whatsThisChar).toString(MixerToolBox::whatsthisControlLocale() ); this->setWhatsThis(w); */ QString whatsthisText = mixDevice()->mixer()->translateKernelToWhatsthis(name); if ( whatsthisText != "---") { setWhatsThis(whatsthisText); } } void MixDeviceWidget::addActionToPopup( QAction *action ) { _mdwActions->addAction( action->objectName(), action ); } void MixDeviceWidget::defineKeys() { // Dialog for *global* shortcuts of this MDW if ( m_shortcutsDialog == 0 ) { m_shortcutsDialog = new KShortcutsDialog( KShortcutsEditor::GlobalAction ); m_shortcutsDialog->addCollection(_mdwPopupActions); } m_shortcutsDialog->configure(); } void MixDeviceWidget::contextMenuEvent(QContextMenuEvent *ev) { showContextMenu(ev->globalPos()); } void MixDeviceWidget::volumeChange( int ) { /* is virtual */ } //void MixDeviceWidget::setDisabled( bool ) { /* is virtual */ } //void MixDeviceWidget::setVolume( int /*channel*/, int /*vol*/ ) { /* is virtual */ } //void MixDeviceWidget::setVolume( Volume /*vol*/ ) { /* is virtual */ } //void MixDeviceWidget::update() { /* is virtual */ } //void MixDeviceWidget::showContextMenu( const QPoint &pos ) { /* is virtual */ } void MixDeviceWidget::setColors( QColor , QColor , QColor ) { /* is virtual */ } void MixDeviceWidget::setIcons( bool ) { /* is virtual */ } void MixDeviceWidget::setLabeled( bool ) { /* is virtual */ } void MixDeviceWidget::setMutedColors( QColor , QColor , QColor ) { /* is virtual */ } diff --git a/gui/toggletoolbutton.cpp b/gui/toggletoolbutton.cpp index 55bbaa45..4024e59c 100644 --- a/gui/toggletoolbutton.cpp +++ b/gui/toggletoolbutton.cpp @@ -1,155 +1,171 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright (C) 2018 Jonathan Marten * * 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, see * . */ #include "toggletoolbutton.h" #include #include - -#include -#include +#include +#include #include -static const KIconLoader::Group iconLoadGroup = KIconLoader::Small; -static const KIconLoader::Group iconSizeGroup = KIconLoader::Toolbar; static const int iconSmallSize = 10; +class DisabledIconEngine : public QIconEngine +{ + QIcon m_icon; +public: + DisabledIconEngine(const QIcon &icon) : QIconEngine(), m_icon(icon) {} + ~DisabledIconEngine() override {} + + QIconEngine *clone() const override + { + return new DisabledIconEngine(m_icon); + } + + void paint(QPainter *painter, const QRect &rect, QIcon::Mode, QIcon::State state) override + { + return m_icon.paint(painter, rect, Qt::AlignCenter, QIcon::Disabled, state); + } + + QPixmap pixmap(const QSize &size, QIcon::Mode, QIcon::State state) override + { + return m_icon.pixmap(size, QIcon::Disabled, state); + } + + QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override + { + return m_icon.actualSize(size, mode, state); + } + + QList availableSizes(QIcon::Mode mode, QIcon::State state) const override + { + return m_icon.availableSizes(mode, state); + } +}; + + ToggleToolButton::ToggleToolButton(const QString &activeIconName, QWidget *pnt) : QToolButton(pnt) { mActiveLoaded = mInactiveLoaded = false; - mActiveIcon = activeIconName; + mActiveIconName = activeIconName; mSmallSize = false; mIsActive = true; mFirstTime = true; setCheckable(false); setAutoRaise(true); } -// based on MDWSlider::setIcon() -QPixmap getPixmap(const QString &name, qreal devicePixelRatio, bool small) -{ - QPixmap pix = KIconLoader::global()->loadScaledIcon(name, iconLoadGroup, devicePixelRatio, IconSize(iconSizeGroup)); - if (!pix.isNull()) // load icon, check success - { // if wanting small size, scale pixmap - if (small) pix = pix.scaled(QSize(iconSmallSize, iconSmallSize) * devicePixelRatio); - } - else qCWarning(KMIX_LOG) << "failed to load" << name; - - // Return the allocated pixmap even if it failed to load, so that - // the caller can tell and only one load attempt will be made. - return (pix); -} - - void ToggleToolButton::setActive(bool active) { if (!mFirstTime && (active==mIsActive)) return; // no change required mIsActive = active; // record required state mFirstTime = false; // note now initialised - QPixmap *pix = nullptr; // new pixmap to set + QIcon icon; // new icon to set if (mIsActive) // look at new state { - if (mActivePixmap.isNull()) // need pixmap for active state + if (mActiveIcon.isNull()) // need icon for active state { // only if not already tried - if (!mActiveLoaded) mActivePixmap = getPixmap(mActiveIcon, devicePixelRatioF(), mSmallSize); + if (!mActiveLoaded) mActiveIcon = QIcon::fromTheme(mActiveIconName); mActiveLoaded = true; // note not to try again } - pix = &mActivePixmap; // the pixmap to use + icon = mActiveIcon; // the icon to use } else // want inactive state { - if (mInactivePixmap.isNull()) // need pixmap for inactive state + if (mInactiveIcon.isNull()) // need icon for inactive state { - if (!mInactiveIcon.isEmpty()) // inactive icon is set + if (!mInactiveIconName.isEmpty()) // inactive icon is set { - if (!mInactiveLoaded) mInactivePixmap = getPixmap(mInactiveIcon, devicePixelRatioF(), mSmallSize); + if (!mInactiveLoaded) mInactiveIcon = QIcon::fromTheme(mInactiveIconName); } else { // need to derive from active state - if (!mActiveLoaded) mActivePixmap = getPixmap(mActiveIcon, devicePixelRatioF(), mSmallSize); + if (!mActiveLoaded) mActiveIcon = QIcon::fromTheme(mActiveIconName); mActiveLoaded = true; // only if not already tried - if (mActivePixmap.isNull()) qCWarning(KMIX_LOG) << "want inactive but no active available"; + if (mActiveIcon.isNull()) qCWarning(KMIX_LOG) << "want inactive but no active available"; else { - mInactivePixmap = KIconLoader::global()->iconEffect()->apply(mActivePixmap, - KIconLoader::Toolbar, - KIconLoader::DisabledState); + mInactiveIcon = QIcon(new DisabledIconEngine(mActiveIcon)); } } mInactiveLoaded = true; // note not to try again } - pix = &mInactivePixmap; // the pixmap to use + icon = mInactiveIcon; // the icon to use } - if (pix->isNull()) return; // pixmap not available - setIcon(*pix); // set button pixmap + if (icon.isNull()) return; // icon not available + setIcon(icon); // set button icon } /** - * Loads the icon with the given @p iconName in the size KIconLoader::Small, + * Loads the icon with the given @p iconName * and applies it to the @p label widget. The widget must be either a * QLabel or a QToolButton. * * Originally @c MDWSlider::setIcon(), moved here because it uses the same * icon size parameters as a @c ToggleToolButton, and it can share @c getPixmap(). */ void ToggleToolButton::setIndicatorIcon(const QString &iconName, QWidget *label, bool small) { - QPixmap pix = getPixmap(iconName, label->devicePixelRatioF(), small); - if (pix.isNull()) + const auto icon = QIcon::fromTheme(iconName); + if (icon.isNull()) { - qCWarning(KMIX_LOG) << "Could not get pixmap for" << iconName; + qCWarning(KMIX_LOG) << "Could not get icon for" << iconName; return; } + QSize iconSize; if (small) // small size, set for scaled icon { - label->resize(iconSmallSize, iconSmallSize); + iconSize = QSize(iconSmallSize, iconSmallSize); } else // not small size, set for normal icon { - label->resize(IconSize(iconSizeGroup), IconSize(iconSizeGroup)); + const auto toolBarIconSize = label->style()->pixelMetric(QStyle::PM_TabBarIconSize, nullptr, label); + iconSize = QSize(toolBarIconSize, toolBarIconSize); } + label->resize(iconSize); label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QLabel *lbl = qobject_cast(label); if (lbl!=nullptr) { - lbl->setPixmap(pix); + lbl->setPixmap(icon.pixmap(iconSize)); lbl->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter); } else { QToolButton *tbt = qobject_cast(label); - if (tbt!=nullptr) tbt->setIcon(pix); // works because implicit QPixmap -> QIcon + if (tbt!=nullptr) tbt->setIcon(icon); } } diff --git a/gui/toggletoolbutton.h b/gui/toggletoolbutton.h index a35b5ba5..a2cd0db8 100644 --- a/gui/toggletoolbutton.h +++ b/gui/toggletoolbutton.h @@ -1,67 +1,67 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright (C) 2018 Jonathan Marten * * 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, see * . */ #ifndef TOGGLETOOLBUTTON_H #define TOGGLETOOLBUTTON_H #include #include /** * A tool button that can maintain two pixmaps, for an active and inactive state. * * @see QToolButton **/ class ToggleToolButton : public QToolButton { Q_OBJECT public: explicit ToggleToolButton(const QString &activeIconName, QWidget *pnt = nullptr); virtual ~ToggleToolButton() = default; void setActive(bool active = true); void setSmallSize(bool small = true) { mSmallSize = small; } bool isActive() const { return (mIsActive); } - void setActiveIcon(const QString &name) { mActiveIcon = name; } - void setInactiveIcon(const QString &name) { mInactiveIcon = name; } + void setActiveIcon(const QString &name) { mActiveIconName = name; } + void setInactiveIcon(const QString &name) { mInactiveIconName = name; } static void setIndicatorIcon(const QString &iconName, QWidget *label, bool small = false); private: bool mSmallSize; - QString mActiveIcon; - QString mInactiveIcon; + QString mActiveIconName; + QString mInactiveIconName; bool mIsActive; bool mFirstTime; - QPixmap mActivePixmap; + QIcon mActiveIcon; bool mActiveLoaded; - QPixmap mInactivePixmap; + QIcon mInactiveIcon; bool mInactiveLoaded; }; #endif // TOGGLETOOLBUTTON_H diff --git a/gui/viewbase.cpp b/gui/viewbase.cpp index d3930da5..e07a5004 100644 --- a/gui/viewbase.cpp +++ b/gui/viewbase.cpp @@ -1,481 +1,480 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * 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 "viewbase.h" // Qt #include #include #include // KDE #include -#include #include #include #include #include // KMix #include "dialogviewconfiguration.h" #include "gui/guiprofile.h" #include "gui/kmixtoolbox.h" #include "gui/mixdevicewidget.h" #include "gui/mdwslider.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/mixer.h" #include "core/mixertoolbox.h" /** * Creates an empty View. To populate it with MixDevice instances, you must implement * initLayout() in your derived class. */ ViewBase::ViewBase(QWidget* parent, QString id, Qt::WindowFlags f, ViewBase::ViewFlags vflags, QString guiProfileId, KActionCollection *actionColletion) : QWidget(parent, f), _popMenu(NULL), _actions(actionColletion), _vflags(vflags), guiLevel(GuiVisibility::Simple), _guiProfileId(guiProfileId) { setObjectName(id); qCDebug(KMIX_LOG) << "id" << id << "flags" << vflags; // When loading the View from the XML profile, guiLevel can get overridden m_viewId = id; if ( _actions == 0 ) { // We create our own action collection, if the actionColletion was 0. // This is currently done for the ViewDockAreaPopup, but only because it has not been converted to use the app-wide // actionCollection(). This is a @todo. _actions = new KActionCollection( this ); } _localActionColletion = new KActionCollection( this ); // Plug in the "showMenubar" action, if the caller wants it. Typically this is only necessary for views in the KMix main window. if ( vflags & ViewBase::HasMenuBar ) { KToggleAction *m = static_cast( _actions->action( name(KStandardAction::ShowMenubar) ) ) ; if ( m != 0 ) { bool visible = ( vflags & ViewBase::MenuBarVisible ); m->setChecked(visible); } } } void ViewBase::addMixer(Mixer *mixer) { _mixers.append(mixer); } QPushButton* ViewBase::createConfigureViewButton() { QPushButton* configureViewButton = new QPushButton(QIcon::fromTheme(QLatin1String("configure")), QString(), this); configureViewButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); configureViewButton->setToolTip(i18n( "Configure this view" )); connect(configureViewButton, SIGNAL(clicked(bool)), SLOT(configureView())); return configureViewButton; } void ViewBase::updateGuiOptions() { setTicks(GlobalConfig::instance().data.showTicks); setLabels(GlobalConfig::instance().data.showLabels); updateMediaPlaybackIcons(); } bool ViewBase::isValid() const { return ( !_mixSet.isEmpty() || isDynamic() ); } void ViewBase::setIcons (bool on) { KMixToolBox::setIcons (_mdws, on ); } void ViewBase::setLabels(bool on) { KMixToolBox::setLabels(_mdws, on ); } void ViewBase::setTicks (bool on) { KMixToolBox::setTicks (_mdws, on ); } /** * Updates all playback icons to their (new) state, e.g. Paused, or Playing */ void ViewBase::updateMediaPlaybackIcons() { for (int i = 0; i < _mdws.count(); ++i) { // Currently media controls are always attached to sliders => use MDWSlider MDWSlider* mdw = qobject_cast(_mdws[i]); if (mdw != 0) { mdw->updateMediaButton(); } } } /** * Create all widgets. * This is a loop over all supported devices of the corresponding view. * On each device add() is called - the derived class must implement add() for creating and placing * the real MixDeviceWidget. * The added MixDeviceWidget is appended to the _mdws list. */ void ViewBase::createDeviceWidgets() { // If this widget is currently visible, it is hidden while the device widgets are // being added and shown again afterwards. This is because, if we are called as // a result of change of orientation or a control being added or removed while we // are visible, isVisibleTo() in ViewSliders::configurationUpdate() appears to // return false even for controls that should be visible. This means that they // do not get included in the label extent calculation and the labels will not // be resized evenly. const bool wasVisible = isVisible(); if (wasVisible) hide(); // hide if currently visible _orientation = orientationSetting(); // refresh orientation from settings qCDebug(KMIX_LOG) << id() << "orientation" << _orientation; initLayout(); foreach (const shared_ptr md, _mixSet) { QWidget *mdw = add(md); // a) Let the implementation do its work _mdws.append(mdw); // b) Add it to the local list connect(mdw, SIGNAL(guiVisibilityChange(MixDeviceWidget*,bool)), SLOT(guiVisibilitySlot(MixDeviceWidget*,bool))); } if (!isDynamic()) { QAction *action = _localActionColletion->addAction("toggle_channels"); action->setText(i18n("Configure Channels...")); connect(action, SIGNAL(triggered(bool)), SLOT(configureView())); } constructionFinished(); // allow view to "polish" itself if (wasVisible) show(); // show again if originally visible } /** * Called when a specific control is to be shown or hidden. At the moment it is only called via * the "hide" action in the MDW context menu. * * @param mdw * @param enable */ void ViewBase::guiVisibilitySlot(MixDeviceWidget* mdw, bool enable) { MixDevice* md = mdw->mixDevice().get(); qCDebug(KMIX_LOG) << "Change " << md->id() << " to visible=" << enable; ProfControl* pctl = findMdw(md->id()); if (pctl == 0) { qCWarning(KMIX_LOG) << "MixDevice not found, and cannot be hidden, id=" << md->id(); return; // Ignore } pctl->setVisible(enable); ControlManager::instance().announce(md->mixer()->id(), ControlManager::ControlList, QString("ViewBase::guiVisibilitySlot")); } // // ---------- Popup stuff START --------------------- void ViewBase::contextMenuEvent(QContextMenuEvent *ev) { showContextMenu(); } /** * Return a popup menu. This contains basic entries. * More can be added by the caller. */ QMenu* ViewBase::getPopup() { popupReset(); return _popMenu; } void ViewBase::popupReset() { QAction *act; delete _popMenu; _popMenu = new QMenu( this ); _popMenu->addSection( QIcon::fromTheme( QLatin1String( "kmix" ) ), i18n("Device Settings" )); act = _localActionColletion->action( "toggle_channels" ); if ( act ) _popMenu->addAction(act); act = _actions->action( "options_show_menubar" ); if ( act ) _popMenu->addAction(act); } /** This will only get executed, when the user has removed all items from the view. Don't remove this method, because then the user cannot get a menu for getting his channels back */ void ViewBase::showContextMenu() { //qCDebug(KMIX_LOG) << "ViewBase::showContextMenu()"; popupReset(); QPoint pos = QCursor::pos(); _popMenu->popup( pos ); } // ---------- Popup stuff END --------------------- void ViewBase::refreshVolumeLevels() { // is virtual } /** * Check all Mixer instances of this view. * If at least on is dynamic, return true. * Please note that usually there is only one Mixer instance per View. * The only exception as of today (June 2011) is the Tray Popup, which * can contain controls from e.g. ALSA and MPRIS2 backends. */ bool ViewBase::isDynamic() const { foreach (Mixer* mixer , _mixers ) { if ( mixer->isDynamic() ) return true; } return false; } bool ViewBase::pulseaudioPresent() const { // We do not use Mixer::pulseaudioPresent(), as we are only interested in Mixer instances contained in this View. foreach (Mixer* mixer , _mixers ) { if ( mixer->getDriverName() == "PulseAudio" ) return true; } return false; } void ViewBase::resetMdws() { // We need to delete the current MixDeviceWidgets so we can recreate them // while (!_mdws.isEmpty()) // delete _mdws.takeFirst(); qDeleteAll(_mdws); _mdws.clear(); // _mixSet contains shared_ptr instances, so clear() should be enough to prevent mem leak _mixSet.clear(); // Clean up our _mixSet so we can reapply our GUIProfile } int ViewBase::visibleControls() const { int visibleCount = 0; foreach (QWidget* qw, _mdws) { if (qw->isVisible()) ++ visibleCount; } return visibleCount; } /** * Open the View configuration dialog. The user can select which channels he wants * to see and which not. */ void ViewBase::configureView() { Q_ASSERT( !isDynamic() ); Q_ASSERT( !pulseaudioPresent() ); DialogViewConfiguration* dvc = new DialogViewConfiguration(0, *this); dvc->show(); } void ViewBase::toggleMenuBarSlot() { //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() start\n"; emit toggleMenuBar(); //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() done\n"; } /** * Loads the configuration of this view. *

* Future directions: The view should probably know its config in advance, so we can use it in #load() and #save() * * * @param config The view for this config */ void ViewBase::load(const KConfig *config) { ViewBase *view = this; QString grp = "View."; grp += view->id(); //KConfigGroup cg = config->group( grp ); qCDebug(KMIX_LOG) << "KMixToolBox::loadView() grp=" << grp.toLatin1(); static const GuiVisibility guiVisibilities[3] = { GuiVisibility::Simple, GuiVisibility::Extended, GuiVisibility::Full }; bool guiLevelSet = false; for (int i = 0; i<3; ++i) { const GuiVisibility guiCompl = guiVisibilities[i]; bool atLeastOneControlIsShown = false; foreach(QWidget *qmdw, view->_mdws) { MixDeviceWidget *mdw = qobject_cast(qmdw); if (mdw!=nullptr) { shared_ptr md = mdw->mixDevice(); QString devgrp = md->configGroupName(grp); KConfigGroup devcg = config->group(devgrp); if (mdw->inherits("MDWSlider")) { // only sliders have the ability to split apart in multiple channels bool splitChannels = devcg.readEntry("Split", !mdw->isStereoLinked()); mdw->setStereoLinked(!splitChannels); } // Future directions: "Visibility" is very dirty: It is read from either config file or // GUIProfile. Thus we have a lot of doubled mdw visibility code all throughout KMix. bool mdwEnabled = false; // Consult GuiProfile for visibility mdwEnabled = findMdw(mdw->mixDevice()->id(), guiCompl) != 0; // Match GUI complexity // qCWarning(KMIX_LOG) << "---------- FIRST RUN: md=" << md->id() << ", guiVisibility=" << guiCompl.getId() << ", enabled=" << mdwEnabled; if (mdwEnabled) { atLeastOneControlIsShown = true; } mdw->setVisible(mdwEnabled); } // inherits MixDeviceWidget } // for all MDW's if (atLeastOneControlIsShown) { guiLevelSet = true; setGuiLevel(guiCompl); break; // If there were controls in this complexity level, don't try more } } // for try = 0 ... 1 if (!guiLevelSet) setGuiLevel(GuiVisibility::Full); } void ViewBase::setGuiLevel(GuiVisibility guiLevel) { this->guiLevel = guiLevel; } /** * Checks whether the given mixDevice shall be shown according to the requested * GuiVisibility. All ProfControl objects are inspected. The first found is returned. * * @param mdwId The control ID * @param requestedGuiComplexityName The GUI name * @return The corresponding ProfControl* * */ ProfControl *ViewBase::findMdw(const QString& mdwId, GuiVisibility visibility) const { foreach ( ProfControl* pControl, guiProfile()->getControls() ) { QRegExp idRegExp(pControl->id()); if ( mdwId.contains(idRegExp) ) { if (pControl->satisfiesVisibility(visibility)) { // qCDebug(KMIX_LOG) << " MATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); return pControl; } else { // qCDebug(KMIX_LOG) << "NOMATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); } } } // iterate over all ProfControl entries return (nullptr); // not found } /* * Saves the View configuration */ void ViewBase::save(KConfig *config) const { const QString grp = "View."+id(); // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) bool dynamic = isDynamic(); // TODO 11 Dynamic view configuration for (int i = 0; i<_mdws.count(); ++i) { MixDeviceWidget *mdw = qobject_cast(_mdws[i]); if (mdw!=nullptr) { shared_ptr md = mdw->mixDevice(); //qCDebug(KMIX_LOG) << " grp=" << grp.toLatin1(); //qCDebug(KMIX_LOG) << " mixer=" << view->id().toLatin1(); //qCDebug(KMIX_LOG) << " mdwPK=" << mdw->mixDevice()->id().toLatin1(); QString devgrp = QString("%1.%2.%3").arg(grp, md->mixer()->id(), md->id()); KConfigGroup devcg = config->group(devgrp); if (mdw->inherits("MDWSlider")) { // only sliders have the ability to split apart in multiple channels devcg.writeEntry("Split", !mdw->isStereoLinked()); } /* if (!dynamic) { devcg.writeEntry("Show", mdw->isVisibleTo(view)); // qCDebug(KMIX_LOG) << "Save devgrp" << devgrp << "show=" << mdw->isVisibleTo(view); } */ } // inherits MixDeviceWidget } // for all MDW's if (!dynamic) { // We do not save GUIProfiles (as they cannot be customized) for dynamic mixers (e.g. PulseAudio) if (guiProfile()->isDirty()) { qCDebug(KMIX_LOG) << "Writing dirty profile. grp=" << grp; guiProfile()->writeProfile(); } } }