diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cc48af9..6e275d3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,352 +1,353 @@ cmake_minimum_required(VERSION 2.8.12) 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.7.0") set (KF5_MIN_VERSION "5.41.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 Plasma Solid WidgetsAddons WindowSystem XmlGui ) 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 needed now? include_directories("/usr/lib/oss/include") # 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) add_subdirectory(plasma) #################################################################################################### ########### 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/mdwmoveaction.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/KMixApp.cpp b/apps/KMixApp.cpp index 22ac91f7..d4c5ff54 100644 --- a/apps/KMixApp.cpp +++ b/apps/KMixApp.cpp @@ -1,225 +1,225 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright (C) 2000 Stefan Schimanski * Copyright (C) 2001 Preston Brown * * 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 "KMixApp.h" #include #include #include "apps/kmix.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" static bool firstCaller = true; // Originally this class was a subclass of KUniqueApplication. // Since, now that a unique application is enforced earlier by KDBusService, // the only purpose of this class is to receive the activateRequested() // signal. It can therefore be a simple QObject. KMixApp::KMixApp() : QObject(), m_kmix(0), creationLock(QMutex::Recursive) { GlobalConfig::init(); // We must disable QuitOnLastWindowClosed. Rationale: // 1) The normal state of KMix is to only have the dock icon shown. // 2a) The dock icon gets reconstructed, whenever a soundcard is hotplugged or unplugged. // 2b) The dock icon gets reconstructed, when the user selects a new master. // 3) During the reconstruction, it can easily happen that no window is present => KMix would quit // => disable QuitOnLastWindowClosed qApp->setQuitOnLastWindowClosed(false); } KMixApp::~KMixApp() { qCDebug(KMIX_LOG) << "Deleting KMixApp"; ControlManager::instance().shutdownNow(); delete m_kmix; m_kmix = 0; GlobalConfig::shutdown(); } void KMixApp::createWindowOnce(bool hasArgKeepvisibility, bool reset) { // Create window, if it was not yet created (e.g. via autostart or manually) if (m_kmix == 0) { qCDebug(KMIX_LOG) << "Creating new KMix window"; m_kmix = new KMixWindow(hasArgKeepvisibility, reset); } } bool KMixApp::restoreSessionIfApplicable(bool hasArgKeepvisibility, bool reset) { /** * We should lock session creation. Rationale: * KMix can be started multiple times during session start. By "autostart" and "session restore". The order is * undetermined, as KMix will initialize in the background of KDE session startup (Hint: As a * KUniqueApplication it decouples from the startkde process!). * * Now we must make sure that window creation is definitely done, before the "other" process comes, as it might * want to restore the session. Working on a half-created window would not be smart! Why can this happen? It * depends on implementation details inside Qt, which COULD potentially lead to the following scenarios: * 1) "Autostart" and "session restore" run concurrently in 2 different Threads. * 2) The current "main/gui" thread "pops up" a "session restore" message from the Qt event dispatcher. * This means that "Autostart" and "session restore" run interleaved in a single Thread. */ creationLock.lock(); bool restore = qApp->isSessionRestored(); // && KMainWindow::canBeRestored(0); qCDebug(KMIX_LOG) << "Starting KMix using keepvisibility=" << hasArgKeepvisibility << ", failsafe=" << reset << ", sessionRestore=" << restore; int createCount = 0; if (restore) { if (reset) { qCWarning(KMIX_LOG) << "Reset cannot be performed while KMix is running. Please quit KMix and retry then."; } int n = 1; while (KMainWindow::canBeRestored(n)) { qCDebug(KMIX_LOG) << "Restoring window " << n; if (n > 1) { // This code path is "impossible". It is here only for analyzing possible issues with session restoring. // KMix is a single-instance app. If more than one instance is created we have a bug. qCWarning(KMIX_LOG) << "KDE session management wants to restore multiple instances of KMix. Please report this as a bug."; break; } else { // Create window, if it was not yet created (e.g. via autostart or manually) createWindowOnce(hasArgKeepvisibility, reset); // #restore() is called with the parameter of "show == false", as KMixWindow itself decides on it. m_kmix->restore(n, false); createCount++; n++; } } } if (createCount == 0) { // Normal start, or if nothing could be restored createWindowOnce(hasArgKeepvisibility, reset); } creationLock.unlock(); return restore; } void KMixApp::newInstance(const QStringList &arguments, const QString &workingDirectory) { - qDebug(); + qCDebug(KMIX_LOG); /** * There are 3 cases when starting KMix: * Autostart : Cases 1) or 3) below * Session restore : Cases 1) or 2a) below * Manual start by user : Cases 1) or 2b) below * * Each may be the creator a new instance, but only if the instance did not exist yet. */ //qCDebug(KMIX_LOG) << "KMixApp::newInstance() isRestored()=" << isRestored() << "_keepVisibility=" << _keepVisibility; /** * NB See https://qa.mandriva.com/show_bug.cgi?id=56893#c3 * * It is important to track this via a separate variable and not * based on m_kmix to handle this race condition. * Specific protection for the activation-prior-to-full-construction * case exists above in the 'already running case' */ creationLock.lock(); // Guard a complete construction bool first = firstCaller; firstCaller = false; if (first) { /** CASE 1 ******************************************************* * * Typical case: Normal start. KMix was not running yet => create a new KMixWindow */ GlobalConfig::init(); restoreSessionIfApplicable(m_hasArgKeepvisibility, m_hasArgReset); } else { if (!m_hasArgKeepvisibility) { /** CASE 2 ****************************************************** * * KMix is running, AND the *USER* starts it again (w/o --keepvisibilty) * 2a) Restored the KMix main window will be shown. * 2b) Not restored */ /* * Restore Session. This may look strange to you, as the instance already exists. But the following * sequence might happen: * 1) Autostart (no restore) => create m_kmix instance (via CASE 1) * 2) Session restore => we are here at this line of code (CASE 2). m_kmix exists, but still must be restored * */ bool wasRestored = restoreSessionIfApplicable(m_hasArgKeepvisibility, m_hasArgReset); if (!wasRestored) { // // Use standard newInstances(), which shows and activates the main window. But skip it for the // special "restored" case, as we should not override the session rules. // TODO: what should be done for KF5? //KUniqueApplication::newInstance(); } // else: Do nothing, as session restore has done it. } else { /** CASE 3 ****************************************************** * * KMix is running, AND launched again with --keepvisibilty * * Typical use case: Autostart * * => We don't want to change the visibility, thus we don't call show() here. * * Hint: --keepVisibility is used in kmix_autostart.desktop. It was used in history by KMilo * (see BKO 58901), but nowadays Mixer Applets might want to use it, though they should * use KMixD instead. */ qCDebug(KMIX_LOG) << "KMixApp::newInstance() REGULAR_START _keepVisibility=" << m_hasArgKeepvisibility; } } creationLock.unlock(); } void KMixApp::parseOptions(const QCommandLineParser &parser) { m_hasArgKeepvisibility = parser.isSet("keepvisibility"); m_hasArgReset = parser.isSet("failsafe"); } diff --git a/apps/kmix.cpp b/apps/kmix.cpp index d0b9ee0e..cd1d55aa 100644 --- a/apps/kmix.cpp +++ b/apps/kmix.cpp @@ -1,1317 +1,1312 @@ /* * 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")); m_hwInfoString = 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("hwinfo")); action->setText(i18n("Hardware &Information")); connect(action, SIGNAL(triggered(bool)), SLOT(slotHWInfo())); 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()->loadIcon("tab-new", KIconLoader::Toolbar, IconSize(KIconLoader::Toolbar)); QPushButton* _cornerLabelNew = new QPushButton(); _cornerLabelNew->setIcon(cornerNewPM); _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; - if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Vertical) - vflags |= ViewBase::Horizontal; - else - vflags |= ViewBase::Vertical; - KMixerWidget *kmw = new KMixerWidget(mixer, this, vflags, guiprofId, actionCollection()); /* A newly added mixer will automatically added at the top * and thus the window title is also set appropriately */ /* * Skip the name from the profile for now. I would at least have to do the '&' quoting for the tab label. But I am * also not 100% sure whether the current name from the profile is any good - it does (likely) not even contain the * card ID. This means you cannot distinguish between cards with an identical name. */ // QString tabLabel = guiprof->getName(); // if (tabLabel.isEmpty()) // QString tabLabel = kmw->mixer()->readableName(true); QString tabLabel = kmw->mixer()->readableName(true); m_dontSetDefaultCardOnStart = true; // inhibit implicit setting of m_defaultCardOnStart if (insertPosition == -1) m_wsMixers->addTab(kmw, tabLabel); else m_wsMixers->insertTab(insertPosition, kmw, tabLabel); if (kmw->getGuiprof()->getId() == m_defaultCardOnStart) { m_wsMixers->setCurrentWidget(kmw); } updateTabsClosable(); m_dontSetDefaultCardOnStart = false; kmw->loadConfig(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::slotHWInfo() { KMessageBox::information(0, m_hwInfoString, i18n("Mixer Hardware Information")); } 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/core/MediaController.cpp b/core/MediaController.cpp index f4740737..c935039f 100644 --- a/core/MediaController.cpp +++ b/core/MediaController.cpp @@ -1,87 +1,83 @@ //-*-C++-*- /* * 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. */ /* * MediaController.cpp * * Created on: 17.12.2013 * Author: chris */ #include "core/MediaController.h" #include "kmix_debug.h" //#include //#include -MediaController::MediaController(QString controlId) : +MediaController::MediaController(const QString &controlId) : id(controlId), playState(PlayUnknown) { mediaPlayControl = false; mediaNextControl = false; mediaPrevControl = false; /* { // Phonon connection test code QList devs = Phonon::BackendCapabilities::availableAudioOutputDevices(); if (devs.isEmpty()) return; Phonon::AudioOutputDevice& dev = devs[0]; QList props = dev.propertyNames(); qCDebug(KMIX_LOG) << "desc=" << dev.description() << ", name=" << dev.name() << ", props="; QByteArray prop; int i=0; foreach (prop, props) { qCDebug(KMIX_LOG) << "#" << i << ": "<< prop; ++i; } } */ } -MediaController::~MediaController() -{ -} - /** * Returns whether this device has at least one media player control. * @return */ -bool MediaController::hasControls() +bool MediaController::hasControls() const { return mediaPlayControl | mediaNextControl | mediaPrevControl; } -MediaController::PlayState MediaController::getPlayState() +MediaController::PlayState MediaController::getPlayState() const { return playState; } void MediaController::setPlayState(PlayState playState) { this->playState = playState; } diff --git a/core/MediaController.h b/core/MediaController.h index 1a8949e7..ae228549 100644 --- a/core/MediaController.h +++ b/core/MediaController.h @@ -1,71 +1,71 @@ //-*-C++-*- /* * 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. */ /* * MediaController.h * * Created on: 17.12.2013 * Author: chris */ #ifndef MEDIACONTROLLER_H_ #define MEDIACONTROLLER_H_ #include #include "kmixcore_export.h" /** * A MediaController controls exactly one Media Player. You can think of it as a single control, like PCM. */ class KMIXCORE_EXPORT MediaController { public: enum PlayState { PlayPaused, PlayPlaying, PlayStopped, PlayUnknown }; - explicit MediaController(QString); - virtual ~MediaController(); + explicit MediaController(const QString &controlId); + virtual ~MediaController() = default; void addMediaPlayControl() { mediaPlayControl = true; }; void addMediaNextControl() { mediaNextControl = true; }; void addMediaPrevControl() { mediaPrevControl = true; }; bool hasMediaPlayControl() { return mediaPlayControl; }; bool hasMediaNextControl() { return mediaNextControl; }; bool hasMediaPrevControl() { return mediaPrevControl; }; - bool hasControls(); + bool hasControls() const; - MediaController::PlayState getPlayState(); + MediaController::PlayState getPlayState() const; void setPlayState(PlayState playState); bool canSkipNext(); bool canSkipPrevious(); private: QString id; PlayState playState; bool mediaPlayControl; bool mediaNextControl; bool mediaPrevControl; }; #endif /* MEDIACONTROLLER_H_ */ diff --git a/core/kmixdevicemanager.cpp b/core/kmixdevicemanager.cpp index e66c4fd5..a98c725e 100644 --- a/core/kmixdevicemanager.cpp +++ b/core/kmixdevicemanager.cpp @@ -1,146 +1,146 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 2006-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 "core/kmixdevicemanager.h" #include #include #include #include "kmix_debug.h" #include #include static const int HOTPLUG_DELAY = 500; // settling delay, milliseconds KMixDeviceManager* KMixDeviceManager::s_KMixDeviceManager = 0; KMixDeviceManager* KMixDeviceManager::instance() { if ( s_KMixDeviceManager == 0 ) { s_KMixDeviceManager = new KMixDeviceManager(); } return s_KMixDeviceManager; } void KMixDeviceManager::initHotplug() { - qDebug(); + qCDebug(KMIX_LOG); connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, this, &KMixDeviceManager::pluggedSlot); connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, this, &KMixDeviceManager::unpluggedSlot); } QString KMixDeviceManager::getUDI_ALSA(int num) { return (QString("hw%1").arg(num)); } QString KMixDeviceManager::getUDI_OSS(const QString &devname) { return (devname); } static bool isSoundDevice(const QString &udi) { QRegExp rx("/sound/"); // any UDI mentioning sound return (udi.contains(rx)); } static int matchDevice(const QString &udi) { QRegExp rx("/sound/card(\\d+)$"); // match sound card and ID number if (!udi.contains(rx)) return (-1); // UDI not recognised return (rx.cap(1).toInt()); // assume conversion succeeds } void KMixDeviceManager::pluggedSlot(const QString &udi) { if (!isSoundDevice(udi)) return; // ignore non-sound devices Solid::Device device(udi); if (!device.isValid()) { qCWarning(KMIX_LOG) << "Invalid device for UDI" << udi; return; } // Solid device UDIs for a plugged sound card are of the form: // // /org/kde/solid/udev/sys/devices/pci0000:00/0000:00:13.2/usb2/2-4/2-4.1/2-4.1:1.0/sound/card3 // /org/kde/solid/udev/sys/devices/pci0000:00/0000:00:13.2/usb2/2-4/2-4.1/2-4.1:1.0/sound/card3/pcmC3D0p // /org/kde/solid/udev/sys/devices/pci0000:00/0000:00:13.2/usb2/2-4/2-4.1/2-4.1:1.0/sound/card3/... // // The first of these is considered to be the canonical form and triggers // device hotplug, after a settling delay. // // Note that the Solid device UDI is not used anwhere else within KMix, // what is referred to as a "UDI" elsewhere is as described in the // comment for Mixer::udi() in core/mixer.h and generated by getUDI_ALSA() // above. Solid only ever supported ALSA anyway, even in KDE4, so we can // assume the driver is ALSA here without having to ask. See // https://community.kde.org/Frameworks/Porting_Notes#Solid_Changes const int dev = matchDevice(udi); if (dev!=-1) { qCDebug(KMIX_LOG) << "udi" << udi; const QString alsaUDI = getUDI_ALSA(dev); qCDebug(KMIX_LOG) << "ALSA udi" << alsaUDI << "device" << dev; QTimer::singleShot(HOTPLUG_DELAY, [=](){ emit plugged("ALSA", alsaUDI, dev); }); } // allow hotplug to settle else qCDebug(KMIX_LOG) << "Ignored unrecognised UDI" << udi; } void KMixDeviceManager::unpluggedSlot(const QString &udi) { if (!isSoundDevice(udi)) return; // ignore non-sound devices // At this point the device has already been unplugged by the user. // Solid doesn't know anything about the device except the UDI, but // that can be matched using the same logic as for plugging. const int dev = matchDevice(udi); if (dev!=-1) { qCDebug(KMIX_LOG) << "udi" << udi; const QString alsaUDI = getUDI_ALSA(dev); qCDebug(KMIX_LOG) << "ALSA udi" << alsaUDI << "device" << dev; QTimer::singleShot(HOTPLUG_DELAY, [=](){ emit unplugged(alsaUDI); }); } // allow hotplug to settle else qCDebug(KMIX_LOG) << "Ignored unrecognised UDI" << udi; } void KMixDeviceManager::setHotpluggingBackends(const QString& backendName) { - qDebug() << "using" << backendName; + qCDebug(KMIX_LOG) << "using" << backendName; _hotpluggingBackend = backendName; } diff --git a/core/mixdevice.cpp b/core/mixdevice.cpp index 7502f247..7e9faef1 100644 --- a/core/mixdevice.cpp +++ b/core/mixdevice.cpp @@ -1,481 +1,476 @@ /* * 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 "core/mixdevice.h" #include #include #include "core/ControlPool.h" #include "core/mixer.h" #include "dbus/dbuscontrolwrapper.h" #include "gui/guiprofile.h" #include "core/volume.h" static const QString channelTypeToIconName( MixDevice::ChannelType type ) { switch (type) { case MixDevice::AUDIO: return "mixer-pcm"; case MixDevice::BASS: case MixDevice::SURROUND_LFE: // "LFE" SHOULD have an own icon return "mixer-lfe"; case MixDevice::CD: return "mixer-cd"; case MixDevice::EXTERNAL: return "mixer-line"; case MixDevice::MICROPHONE: return "mixer-microphone"; case MixDevice::MIDI: return "mixer-midi"; case MixDevice::RECMONITOR: return "mixer-capture"; case MixDevice::TREBLE: return "mixer-pcm-default"; case MixDevice::UNKNOWN: return "mixer-front"; case MixDevice::VOLUME: return "mixer-master"; case MixDevice::VIDEO: return "mixer-video"; case MixDevice::SURROUND: case MixDevice::SURROUND_BACK: return "mixer-surround"; case MixDevice::SURROUND_CENTERFRONT: case MixDevice::SURROUND_CENTERBACK: return "mixer-surround-center"; case MixDevice::HEADPHONE: return "mixer-headset"; case MixDevice::DIGITAL: return "mixer-digital"; case MixDevice::AC97: return "mixer-ac97"; case MixDevice::SPEAKER: return "mixer-pc-speaker"; case MixDevice::MICROPHONE_BOOST: return "mixer-microphone-boost"; case MixDevice::MICROPHONE_FRONT_BOOST: return "mixer-microphone-front-boost"; case MixDevice::MICROPHONE_FRONT: return "mixer-microphone-front"; case MixDevice::KMIX_COMPOSITE: return "mixer-line"; case MixDevice::APPLICATION_AMAROK: return "amarok"; case MixDevice::APPLICATION_BANSHEE: return "media-player-banshee"; case MixDevice::APPLICATION_XMM2: return "xmms"; case MixDevice::APPLICATION_TOMAHAWK: return "tomahawk"; case MixDevice::APPLICATION_CLEMENTINE: return "application-x-clementine"; case MixDevice::APPLICATION_VLC: return "vlc"; case MixDevice::APPLICATION_STREAM: return "mixer-pcm"; } return "mixer-front"; } /** * Constructs a MixDevice. A MixDevice represents one channel or control of * the mixer hardware. A MixDevice has a type (e.g. PCM), a descriptive name * (for example "Master" or "Headphone" or "IEC 958 Output"), * can have a volume level (2 when stereo), can be recordable and muted. * The ChannelType tells which kind of control the MixDevice is. */ MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type ) : _profControl(0) { init(mixer, id, name, channelTypeToIconName(type), nullptr); } MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ) : _profControl(0) { init(mixer, id, name, iconName, moveDestinationMixSet); } void MixDevice::init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ) { _artificial = false; _applicationStream = false; _dbusControlWrapper = 0; // will be set in addToPool() _mixer = mixer; _id = id; _enumCurrentId = 0; mediaController = new MediaController(_id); if( name.isEmpty() ) _name = i18n("unknown"); else _name = name; if ( iconName.isEmpty() ) _iconName = "mixer-front"; else _iconName = iconName; _moveDestinationMixSet = moveDestinationMixSet; if ( _id.contains(' ') ) { // The key is used in the config file. IdbusControlWrappert MUST NOT contain spaces qCCritical(KMIX_LOG) << "MixDevice::setId(\"" << id << "\") . Invalid key - it must not contain spaces"; _id.replace(' ', '_'); } // qCDebug(KMIX_LOG) << "MixDevice::init() _id=" << _id; } /* * When a MixDevice shall be finally discarded, you must use this method to free its resources. * You must not use this MixDevice after calling close(). *
* The necessity stems from a memory leak due to object cycle (MixDevice<->DBusControlWrapper), so the reference * counting shared_ptr has no chance to clean up. See Bug 309464 for background information. */ void MixDevice::close() { delete _dbusControlWrapper; _dbusControlWrapper = 0; } MediaController* MixDevice::getMediaController() { return mediaController; } shared_ptr MixDevice::addToPool() { // qCDebug(KMIX_LOG) << "id=" << _mixer->id() << ":" << _id; shared_ptr thisSharedPtr(this); //shared_ptr thisSharedPtr = ControlPool::instance()->add(fullyQualifiedId, this); _dbusControlWrapper = new DBusControlWrapper( thisSharedPtr, dbusPath() ); return thisSharedPtr; } /** * Changes the internal state of this MixDevice. * It does not commit the change to the hardware. * * You might want to call something like m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); after calling this method. */ void MixDevice::increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType) { bool debugme = false; // bool debugme = id() == "PCM:0" ; if (volumeType & Volume::Playback) { Volume& volP = playbackVolume(); long inc = volP.volumeStep(decrease); if (debugme) qCDebug(KMIX_LOG) << ( decrease ? "decrease by " : "increase by " ) << inc ; if (isMuted()) { setMuted(false); } else { volP.changeAllVolumes(inc); if (debugme) qCDebug(KMIX_LOG) << (decrease ? "decrease by " : "increase by ") << inc; } } if (volumeType & Volume::Capture) { if (debugme) qCDebug(KMIX_LOG) << "VolumeType=" << volumeType << " c"; Volume& volC = captureVolume(); long inc = volC.volumeStep(decrease); volC.changeAllVolumes(inc); } } /** * Returns the name of the config group * @param Prefix of the group, e.g. "View_ALSA_USB_01" * @returns The config group name in the format "prefix.mixerId.controlId" */ QString MixDevice::configGroupName(QString prefix) { QString devgrp = QString("%1.%2.%3").arg(prefix, mixer()->id(), id()); return devgrp; } /** * Returns a fully qualified id of this control, as a String in the form "controlId@mixerId" * @return */ QString MixDevice::getFullyQualifiedId() { QString fqId = QString("%1@%2").arg(_id, _mixer->id()); return fqId; } /** * Creates a deep copy of the given Volume, and adds it to this MixDevice. * * @param playbackVol */ void MixDevice::addPlaybackVolume(Volume &playbackVol) { // Hint: "_playbackVolume" gets COPIED from "playbackVol", because the copy-constructor actually copies the volume levels. _playbackVolume = playbackVol; _playbackVolume.setSwitchType(Volume::PlaybackSwitch); } /** * Creates a deep copy of the given Volume, and adds it to this MixDevice. * * @param captureVol */ void MixDevice::addCaptureVolume (Volume &captureVol) { _captureVolume = captureVol; _captureVolume.setSwitchType(Volume::CaptureSwitch); } void MixDevice::addEnums(QList& ref_enumList) { if ( ref_enumList.count() > 0 ) { int maxEnumId = ref_enumList.count(); for (int i=0; i& MixDevice::enumValues() { - return _enumValues; -} - - const QString& MixDevice::id() const { return _id; } const QString MixDevice::dbusPath() { QString controlPath = _id; controlPath.replace(QRegExp("[^a-zA-Z0-9_]"), "_"); controlPath.replace(QLatin1String("//"), QLatin1String("/")); if ( controlPath.endsWith( '/' ) ) { controlPath.chop(1); } return _mixer->dbusPath() + '/' + controlPath; } bool MixDevice::isMuted() { return ! _playbackVolume.isSwitchActivated(); } /** * Returns whether this MixDevice is virtually muted. Only MixDevice objects w/o a physical switch can be muted virtually. */ bool MixDevice::isVirtuallyMuted() { return !hasPhysicalMuteSwitch() && isMuted(); } void MixDevice::setMuted(bool mute) { _playbackVolume.setSwitch(!mute); } void MixDevice::toggleMute() { setMuted( _playbackVolume.isSwitchActivated()); } bool MixDevice::hasMuteSwitch() { return playbackVolume().hasVolume() || playbackVolume().hasSwitch(); } bool MixDevice::hasPhysicalMuteSwitch() { return playbackVolume().hasSwitch(); } bool MixDevice::isRecSource() { return ( _captureVolume.hasSwitch() && _captureVolume.isSwitchActivated() ); } bool MixDevice::isNotRecSource() { return ( _captureVolume.hasSwitch() && !_captureVolume.isSwitchActivated() ); } void MixDevice::setRecSource(bool value) { _captureVolume.setSwitch( value ); } bool MixDevice::isEnum() { return ( ! _enumValues.empty() ); } int MixDevice::mediaPlay() { return mixer()->mediaPlay(_id); } int MixDevice::mediaPrev() { return mixer()->mediaPrev(_id); } int MixDevice::mediaNext() { return mixer()->mediaNext(_id); } bool MixDevice::operator==(const MixDevice& other) const { return ( _id == other._id ); } void MixDevice::setControlProfile(ProfControl* control) { _profControl = control; } ProfControl* MixDevice::controlProfile() { return _profControl; } /** * This method is currently only called on "kmixctrl --restore" * * Normally we have a working _volume object already, which is very important, * because we need to read the minimum and maximum volume levels. * (Another solution would be to "equip" volFromConfig with maxInt and minInt values). */ bool MixDevice::read( KConfig *config, const QString& grp ) { if ( _mixer->isDynamic() || isArtificial() ) { qCDebug(KMIX_LOG) << "MixDevice::read(): This MixDevice does not permit volume restoration (i.e. because it is handled lower down in the audio stack). Ignoring."; return false; } QString devgrp = QString("%1.Dev%2").arg(grp, _id); KConfigGroup cg = config->group( devgrp ); //qCDebug(KMIX_LOG) << "MixDevice::read() of group devgrp=" << devgrp; readPlaybackOrCapture(cg, false); readPlaybackOrCapture(cg, true ); bool mute = cg.readEntry("is_muted", false); setMuted( mute ); bool recsrc = cg.readEntry("is_recsrc", false); setRecSource( recsrc ); int enumId = cg.readEntry("enum_id", -1); if ( enumId != -1 ) { setEnumId( enumId ); } return true; } void MixDevice::readPlaybackOrCapture(const KConfigGroup& config, bool capture) { Volume& volume = capture ? captureVolume() : playbackVolume(); for ( Volume::ChannelID chid=Volume::CHIDMIN; chid<= Volume::CHIDMAX; ) { QString volstr = getVolString(chid,capture); if ( config.hasKey(volstr) ) { volume.setVolume(chid, config.readEntry(volstr, 0)); } // if saved channel exists // TODO: ugly, so find a better way (implement Volume::ChannelID::operator++) chid = static_cast(1+static_cast(chid)); } // for all channels } /** * called on "kmixctrl --save" and from the GUI's (currently only on exit) */ bool MixDevice::write( KConfig *config, const QString& grp ) { if (_mixer->isDynamic() || isArtificial()) { // qCDebug(KMIX_LOG) << "MixDevice::write(): This MixDevice does not permit volume saving (i.e. because it is handled lower down in the audio stack). Ignoring."; return false; } QString devgrp = QString("%1.Dev%2").arg(grp, _id); KConfigGroup cg = config->group(devgrp); // qCDebug(KMIX_LOG) << "MixDevice::write() of group devgrp=" << devgrp; writePlaybackOrCapture(cg, false); writePlaybackOrCapture(cg, true ); cg.writeEntry("is_muted" , isMuted() ); cg.writeEntry("is_recsrc", isRecSource() ); cg.writeEntry("name", _name); if ( isEnum() ) { cg.writeEntry("enum_id", enumId() ); } return true; } void MixDevice::writePlaybackOrCapture(KConfigGroup& config, bool capture) { Volume& volume = capture ? captureVolume() : playbackVolume(); foreach (VolumeChannel vc, volume.getVolumes() ) { config.writeEntry(getVolString(vc.chid,capture), static_cast(vc.volume)); } // for all channels } QString MixDevice::getVolString(Volume::ChannelID chid, bool capture) { QString volstr = Volume::channelNameForPersistence(chid); if ( capture ) volstr += "Capture"; return volstr; } /** * Returns the playback volume level in percent. If the volume is muted, 0 is returned. * If the given MixDevice contains no playback volume, the capture volume isd used * instead, and 0 is returned if capturing is disabled for the given MixDevice. * * @returns The volume level in percent */ int MixDevice::getUserfriendlyVolumeLevel() { MixDevice* md = this; bool usePlayback = md->playbackVolume().hasVolume(); Volume& vol = usePlayback ? md->playbackVolume() : md->captureVolume(); bool isActive = usePlayback ? !md->isMuted() : md->isRecSource(); int val = isActive ? vol.getAvgVolumePercent(Volume::MALL) : 0; return val; } diff --git a/core/mixdevice.h b/core/mixdevice.h index f563832c..0dfc16bc 100644 --- a/core/mixdevice.h +++ b/core/mixdevice.h @@ -1,263 +1,263 @@ //-*-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 MixDevice_h #define MixDevice_h #include #if defined(HAVE_STD_SHARED_PTR) #include using std::shared_ptr; #elif defined(HAVE_STD_TR1_SHARED_PTR) #include using std::tr1::shared_ptr; #endif //KMix #include "core/MediaController.h" class Mixer; class MixSet; class ProfControl; #include "core/volume.h" #include "kmixcore_export.h" class DBusControlWrapper; // KDE #include #include // Qt #include #include #include /** * This is the abstraction of a single control of a sound card, e.g. the PCM control. A control * can contain the 5 following subcontrols: playback-volume, capture-volume, playback-switch, * capture-switch and enumeration. The class is called MixDevice for historical reasons. Today it is just the Synonym for "Control". Design hint: In the past I (esken) considered merging the MixDevice and Volume classes. I finally decided against it, as it seems better to have the MixDevice being the container for the embedded subcontrol(s). These could be either Volume, Enum or some virtual MixDevice. */ class KMIXCORE_EXPORT MixDevice : public QObject { Q_OBJECT public: // For each ChannelType a special icon exists enum ChannelType { AUDIO = 1, BASS, CD, EXTERNAL, MICROPHONE, MIDI, RECMONITOR, TREBLE, UNKNOWN, VOLUME, VIDEO, SURROUND, HEADPHONE, DIGITAL, AC97, SURROUND_BACK, SURROUND_LFE, SURROUND_CENTERFRONT, SURROUND_CENTERBACK, SPEAKER, MICROPHONE_BOOST, MICROPHONE_FRONT_BOOST, MICROPHONE_FRONT, KMIX_COMPOSITE, APPLICATION_STREAM, // Some specific applications APPLICATION_AMAROK, APPLICATION_BANSHEE, APPLICATION_XMM2, APPLICATION_TOMAHAWK, APPLICATION_CLEMENTINE, // Hint: VLC still has compatibility problems: // 2.0 is not detected // 2.2-nightly has volume issues (total overdrive) APPLICATION_VLC, }; enum SwitchType { OnOff, Mute, Capture, Activator }; /** * Constructor for a MixDevice. * After having constructed a MixDevice, you must add it to the ControlPool * by calling addToPool(). You may then not delete this object. * * @par mixer The mixer this control belongs to * @par id Defines the ID, e.g. used in looking up the keys in kmixrc. Also it is used heavily inside KMix as unique key. * It is advised to set a nice name, like 'PCM:2', which would mean * "2nd PCM device of the sound card". The ID's may NOT contain whitespace. * The Creator (normally the backend) MUST pass distinct ID's for each MixDevices of one card. * * Virtual Controls (controls not created by a backend) are prefixed with "KMix::", e.g. * "KMix::RecSelector:0" * @par name is the readable name. This one is presented to the user in the GUI * @par type The control type. It is only used to find an appropriate icon */ MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type ); MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName = "", MixSet* moveDestinationMixSet = 0 ); ~MixDevice(); void close(); shared_ptr addToPool(); const QString& iconName() const { return _iconName; } void addPlaybackVolume(Volume &playbackVol); void addCaptureVolume (Volume &captureVol); void addEnums (QList& ref_enumList); // Media controls. New for KMix 4.0 MediaController* getMediaController(); // TODO move all media player controls to the MediaController class int mediaPlay(); int mediaPrev(); int mediaNext(); // Returns a user readable name of the control. QString readableName() { return _name; } // Sets a user readable name for the control. - void setReadableName(QString& name) { _name = name; } + void setReadableName(const QString &name) { _name = name; } QString configGroupName(QString prefix); /** * Returns an ID of this MixDevice, as passed in the constructor. The Creator (normally the backend) * MUST ensure that all MixDevices's of one card have unique ID's. * The ID is used through the whole KMix application (including the config file) for identifying controls. */ const QString& id() const; QString getFullyQualifiedId(); /** * Returns the DBus path for this MixDevice */ const QString dbusPath(); // Returns the associated mixer Mixer* mixer() { return _mixer; } // operator==() is used currently only for duplicate detection with QList's contains() method bool operator==(const MixDevice& other) const; // Methods for handling the switches. This methods are useful, because the Switch in the Volume object // is an abstract concept. It places no interpretation on the meaning of the switch (e.g. does "switch set" mean // "mute on", or does it mean "playback on", or "Capture active", or ... virtual bool isMuted(); virtual bool isVirtuallyMuted(); virtual void setMuted(bool value); virtual bool hasMuteSwitch(); virtual void toggleMute(); virtual bool isRecSource(); virtual bool isNotRecSource(); virtual void setRecSource(bool value); virtual bool isEnum(); /** * Returns whether this is an application stream. */ virtual bool isApplicationStream() const { return _applicationStream; }; /** * Mark this MixDevice as application stream */ void setApplicationStream(bool applicationStream) { _applicationStream = applicationStream; } bool isMovable() const { return (0 != _moveDestinationMixSet); } MixSet *getMoveDestinationMixSet() const { return _moveDestinationMixSet; } bool isArtificial() const { return _artificial; } void setArtificial(bool artificial) { _artificial = artificial; } void setControlProfile(ProfControl* control); ProfControl* controlProfile(); virtual Volume& playbackVolume(); virtual Volume& captureVolume(); void setEnumId(int); unsigned int enumId(); - QList& enumValues(); + const QStringList &enumValues() const { return (_enumValues); } bool hasPhysicalMuteSwitch(); bool read( KConfig *config, const QString& grp ); bool write( KConfig *config, const QString& grp ); int getUserfriendlyVolumeLevel(); void increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType); protected: void init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ); private: QString getVolString(Volume::ChannelID chid, bool capture); Mixer *_mixer; Volume _playbackVolume; Volume _captureVolume; int _enumCurrentId; - QList _enumValues; // A MixDevice, that is an ENUM, has these _enumValues + QStringList _enumValues; // A MixDevice, that is an ENUM, has these _enumValues DBusControlWrapper *_dbusControlWrapper; MediaController* mediaController; // A virtual control. It will not be saved/restored and/or doesn't get shortcuts // Actually we discriminate those "virtual" controls in artificial controls and dynamic controls: // Type Shortcut Restore // Artificial: yes no Virtual::GlobalMaster or Virtual::CaptureGroup_3 (controls that are constructed artificially from other controls) // Dynamic : no no Controls that come and go, like Pulse Stream controls bool _artificial; MixSet *_moveDestinationMixSet; QString _iconName; bool _applicationStream; QString _name; // Channel name QString _id; // Primary key, used as part in config file keys ProfControl *_profControl; void readPlaybackOrCapture(const KConfigGroup& config, bool capture); void writePlaybackOrCapture(KConfigGroup& config, bool capture); }; #endif diff --git a/core/mixer.cpp b/core/mixer.cpp index f118b80d..38698c08 100644 --- a/core/mixer.cpp +++ b/core/mixer.cpp @@ -1,719 +1,720 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken - esken@kde.org * 2002 Helio Chissini de Castro - helio@conectiva.com.br * * 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 "core/mixer.h" #include #include #include "backends/mixer_backend.h" #include "backends/kmix-backends.cpp" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/volume.h" /** * Some general design hints. Hierarchy is Mixer->MixDevice->Volume */ QList Mixer::s_mixers; MasterControl Mixer::_globalMasterCurrent; MasterControl Mixer::_globalMasterPreferred; bool Mixer::m_beepOnVolumeChange = false; int Mixer::numDrivers() { MixerFactory *factory = g_mixerFactories; int num = 0; while( factory->getMixer!=0 ) { num++; factory++; } return num; } /* * Returns a reference of the current mixer list. */ QList& Mixer::mixers() { return s_mixers; } /** * Returns whether there is at least one dynamic mixer active. * @returns true, if at least one dynamic mixer is active */ bool Mixer::dynamicBackendsPresent() { foreach ( Mixer* mixer, Mixer::mixers() ) { if ( mixer->isDynamic() ) return true; } return false; } bool Mixer::pulseaudioPresent() { foreach ( Mixer* mixer, Mixer::mixers() ) { if ( mixer->getDriverName() == "PulseAudio" ) return true; } return false; } Mixer::Mixer(const QString &ref_driverName, int device) : m_balance(0), _mixerBackend(nullptr), m_dynamic(false) { _mixerBackend = 0; int driverCount = numDrivers(); for (int driver=0; driver retrieve Mixer factory for that driver getMixerFunc *f = g_mixerFactories[driver].getMixer; if( f!=0 ) { _mixerBackend = f( this, device ); readSetFromHWforceUpdate(); // enforce an initial update on first readSetFromHW() } break; } } } Mixer::~Mixer() { // Close the mixer. This might also free memory, depending on the called backend method close(); delete _mixerBackend; } /* * Find a Mixer. If there is no mixer with the given id, 0 is returned */ Mixer* Mixer::findMixer( const QString& mixer_id) { Mixer *mixer = 0; int mixerCount = Mixer::mixers().count(); for ( int i=0; iid() == mixer_id ) { mixer = (Mixer::mixers())[i]; break; } } return mixer; } /** * Set the final ID of this Mixer. *
Warning: This method is VERY fragile, because it is requires information that we have very late, * especially the _cardInstance. We only know the _cardInstance, when we know the ID of the _mixerBackend->getId(). * OTOH, the Mixer backend needs the _cardInstance during construction of its MixDevice instances. * * This means, we need the _cardInstance during construction of the Mixer, but we only know it after its constructed. * Actually its a design error. The _cardInstance MUST be set and managed by the backend. * * The current solution works but is very hacky - cardInstance is a parameter of openIfValid(). * */ void Mixer::recreateId() { /* As we use "::" and ":" as separators, the parts %1,%2 and %3 may not * contain it. * %1, the driver name is from the KMix backends, it does not contain colons. * %2, the mixer name, is typically coming from an OS driver. It could contain colons. * %3, the mixer number, is a number: it does not contain colons. */ QString mixerName = _mixerBackend->getId(); mixerName.replace(':','_'); QString primaryKeyOfMixer = QString("%1::%2:%3") .arg(getDriverName(), mixerName) .arg(getCardInstance()); // The following 3 replaces are for not messing up the config file primaryKeyOfMixer.replace(']','_'); primaryKeyOfMixer.replace('[','_'); // not strictly necessary, but lets play safe primaryKeyOfMixer.replace(' ','_'); primaryKeyOfMixer.replace('=','_'); _id = primaryKeyOfMixer; // qCDebug(KMIX_LOG) << "Early _id=" << _id; } const QString Mixer::dbusPath() { // _id needs to be fixed from the very beginning, as the MixDevice construction uses MixDevice::dbusPath(). // So once the first MixDevice is created, this must return the correct value if (_id.isEmpty()) { if (! _mixerBackend->_cardRegistered) { // Bug 308014: By checking _cardRegistered, we can be sure that everything is fine, including the fact that // the cardId (aka "card instance") is set. If _cardRegistered would be false, we will create potentially // wrong/duplicated DBUS Paths here. qCWarning(KMIX_LOG) << "Mixer id was empty when creating DBUS path. Emergency code created the id=" <<_id; } // Bug 308014: Actually this a shortcut (you could also call it a hack). It would likely better if registerCard() // would create the Id, but it requires cooperation from ALL backends. Also Mixer->getId() would need to // proxy that to the backend. // So for now we lazily create the MixerId here, while creating the first MixDevice for that card. recreateId(); } // mixerName may contain arbitrary characters, so replace all that are not allowed to be be part of a DBUS path QString cardPath = _id; cardPath.replace(QRegExp("[^a-zA-Z0-9_]"), "_"); cardPath.replace(QLatin1String("//"), QLatin1String("/")); return QString("/Mixers/" + cardPath); } void Mixer::volumeSave( KConfig *config ) { // qCDebug(KMIX_LOG) << "Mixer::volumeSave()"; _mixerBackend->readSetFromHW(); QString grp("Mixer"); grp.append(id()); _mixerBackend->m_mixDevices.write( config, grp ); // This might not be the standard application config object // => Better be safe and call sync(). config->sync(); } void Mixer::volumeLoad( KConfig *config ) { QString grp("Mixer"); grp.append(id()); if ( ! config->hasGroup(grp) ) { // no such group. Volumes (of this mixer) were never saved beforehand. // Thus don't restore anything (also see Bug #69320 for understanding the real reason) return; // make sure to bail out immediately } // else restore the volumes if ( ! _mixerBackend->m_mixDevices.read( config, grp ) ) { // Some mixer backends don't support reading the volume into config // files, so bail out early if that's the case. return; } // set new settings for(int i=0; i<_mixerBackend->m_mixDevices.count() ; i++ ) { shared_ptr md = _mixerBackend->m_mixDevices[i]; if ( md.get() == 0 ) continue; _mixerBackend->writeVolumeToHW( md->id(), md ); if ( md->isEnum() ) _mixerBackend->setEnumIdHW( md->id(), md->enumId() ); } } /** * Opens the mixer. * Also, starts the polling timer, for polling the Volumes from the Mixer. * * @return true, if Mixer could be opened. */ bool Mixer::openIfValid() { if (_mixerBackend==nullptr) { // If we did not instantiate a suitable backend, then the mixer is invalid. qCWarning(KMIX_LOG) << "no mixer backend"; return false; } bool ok = _mixerBackend->openIfValid(); if (!ok) return (false); recreateId(); shared_ptr recommendedMaster = _mixerBackend->recommendedMaster(); if (recommendedMaster.get()!=nullptr) { QString recommendedMasterStr = recommendedMaster->id(); setLocalMasterMD( recommendedMasterStr ); qCDebug(KMIX_LOG) << "Detected master" << recommendedMaster->id(); } else { if (!m_dynamic) qCCritical(KMIX_LOG) << "No master detected and not dynamic"; else qCDebug(KMIX_LOG) << "No master detected but dynamic"; QString noMaster = "---no-master-detected---"; setLocalMasterMD(noMaster); // no master } new DBusMixerWrapper(this, dbusPath()); return (true); } /** * Closes the mixer. */ void Mixer::close() { if ( _mixerBackend != 0) _mixerBackend->closeCommon(); } /* ------- WRAPPER METHODS. START ------------------------------ */ unsigned int Mixer::size() const { return _mixerBackend->m_mixDevices.count(); } shared_ptr Mixer::operator[](int num) { shared_ptr md = _mixerBackend->m_mixDevices.at( num ); return md; } MixSet& Mixer::getMixSet() { return _mixerBackend->m_mixDevices; } /** * Returns the driver name, that handles this Mixer. */ -QString Mixer::getDriverName() +QString Mixer::getDriverName() const { QString driverName = _mixerBackend->getDriverName(); // qCDebug(KMIX_LOG) << "Mixer::getDriverName() = " << driverName << "\n"; return driverName; } bool Mixer::isOpen() const { if ( _mixerBackend == 0 ) return false; else return _mixerBackend->isOpen(); } void Mixer::readSetFromHWforceUpdate() const { _mixerBackend->readSetFromHWforceUpdate(); } /// Returns translated WhatsThis messages for a control.Translates from QString Mixer::translateKernelToWhatsthis(const QString &kernelName) { return _mixerBackend->translateKernelToWhatsthis(kernelName); } /* ------- WRAPPER METHODS. END -------------------------------- */ int Mixer::balance() const { return m_balance; } void Mixer::setBalance(int balance) { if( balance == m_balance ) { // balance unchanged => return return; } m_balance = balance; shared_ptr master = getLocalMasterMD(); if ( master.get() == 0 ) { // no master device available => return return; } Volume& volP = master->playbackVolume(); setBalanceInternal(volP); Volume& volC = master->captureVolume(); setBalanceInternal(volC); _mixerBackend->writeVolumeToHW( master->id(), master ); emit newBalance( volP ); } void Mixer::setBalanceInternal(Volume& vol) { //_mixerBackend->readVolumeFromHW( master->id(), master ); int left = vol.getVolume(Volume::LEFT); int right = vol.getVolume( Volume::RIGHT ); int refvol = left > right ? left : right; if( m_balance < 0 ) // balance left { vol.setVolume( Volume::LEFT, refvol); vol.setVolume( Volume::RIGHT, (m_balance * refvol) / 100 + refvol ); } else { vol.setVolume( Volume::LEFT, -(m_balance * refvol) / 100 + refvol ); vol.setVolume( Volume::RIGHT, refvol); } } /** * Returns a name suitable for a human user to read (on a label, ...) */ -QString Mixer::readableName(bool ampersandQuoted) +QString Mixer::readableName(bool ampersandQuoted) const { QString finalName = _mixerBackend->getName(); if (ampersandQuoted) finalName.replace('&', "&&"); if ( getCardInstance() > 1) finalName = finalName.append(" %1").arg(getCardInstance()); // qCDebug(KMIX_LOG) << "name=" << _mixerBackend->getName() << "instance=" << getCardInstance() << ", finalName" << finalName; return finalName; } -QString Mixer::getBaseName() +QString Mixer::getBaseName() const { return _mixerBackend->getName(); } /** * Queries the Driver Factory for a driver. * @par driver Index number. 0 <= driver < numDrivers() */ QString Mixer::driverName( int driver ) { getDriverNameFunc *f = g_mixerFactories[driver].getDriverName; if( f!=0 ) return f(); else return "unknown"; } /* obsoleted by setInstance() void Mixer::setID(QString& ref_id) { _id = ref_id; } */ -QString& Mixer::id() +const QString &Mixer::id() const { return _id; } -QString& Mixer::udi(){ +const QString &Mixer::udi() const +{ return _mixerBackend->udi(); } /** * Set the global master, which is shown in the dock area and which is accessible via the * DBUS masterVolume() method. * * The parameters are taken over as-is, this means without checking for validity. * This allows the User to define a master card that is not always available * (e.g. it is an USB hotplugging device). Also you can set the master at any time you * like, e.g. after reading the KMix configuration file and before actually constructing * the Mixer instances (hint: this method is static!). * * @param ref_card The card id * @param ref_control The control id. The corresponding control must be present in the card. * @param preferred Whether this is the preferred master (auto-selected on coldplug and hotplug). */ void Mixer::setGlobalMaster(QString ref_card, QString ref_control, bool preferred) { qCDebug(KMIX_LOG) << "ref_card=" << ref_card << ", ref_control=" << ref_control << ", preferred=" << preferred; _globalMasterCurrent.set(ref_card, ref_control); if ( preferred ) _globalMasterPreferred.set(ref_card, ref_control); qCDebug(KMIX_LOG) << "Mixer::setGlobalMaster() card=" <id(); return mixer; } /** * Return the preferred global master. * If there is no preferred global master, returns the current master instead. */ MasterControl& Mixer::getGlobalMasterPreferred(bool fallbackAllowed) { static MasterControl result; if ( !fallbackAllowed || _globalMasterPreferred.isValid() ) { // qCDebug(KMIX_LOG) << "Returning preferred master"; return _globalMasterPreferred; } Mixer* mm = Mixer::getGlobalMasterMixerNoFalback(); if (mm) { result.set(_globalMasterPreferred.getCard(), mm->getRecommendedDeviceId()); if (!result.getControl().isEmpty()) // qCDebug(KMIX_LOG) << "Returning extended preferred master"; return result; } qCDebug(KMIX_LOG) << "Returning current master"; return _globalMasterCurrent; } shared_ptr Mixer::getGlobalMasterMD() { return getGlobalMasterMD(true); } shared_ptr Mixer::getGlobalMasterMD(bool fallbackAllowed) { shared_ptr mdRet; shared_ptr firstDevice; Mixer *mixer = fallbackAllowed ? Mixer::getGlobalMasterMixer() : Mixer::getGlobalMasterMixerNoFalback(); if ( mixer == 0 ) return mdRet; if (_globalMasterCurrent.getControl().isEmpty()) { // Default (recommended) control return mixer->_mixerBackend->recommendedMaster(); } foreach (shared_ptr md, mixer->_mixerBackend->m_mixDevices ) { if ( md.get() == 0 ) continue; // invalid firstDevice=md; if ( md->id() == _globalMasterCurrent.getControl() ) { mdRet = md; break; // found } } if ( mdRet.get() == 0 ) { //For some sound cards when using pulseaudio the mixer id is not proper hence returning the first device as master channel device //This solves the bug id:290177 and problems stated in review #105422 qCDebug(KMIX_LOG) << "Mixer::masterCardDevice() returns 0 (no globalMaster), returning the first device"; mdRet=firstDevice; } return mdRet; } QString Mixer::getRecommendedDeviceId() { if ( _mixerBackend != 0 ) { shared_ptr recommendedMaster = _mixerBackend->recommendedMaster(); if ( recommendedMaster.get() != 0 ) return recommendedMaster->id(); } return QString(); } -shared_ptr Mixer::getLocalMasterMD() +shared_ptr Mixer::getLocalMasterMD() const { if (_mixerBackend && _masterDevicePK.isEmpty()) return _mixerBackend->recommendedMaster(); return find( _masterDevicePK ); } void Mixer::setLocalMasterMD(QString &devPK) { _masterDevicePK = devPK; } -shared_ptr Mixer::find(const QString& mixdeviceID) +shared_ptr Mixer::find(const QString& mixdeviceID) const { shared_ptr mdRet; foreach (shared_ptr md, _mixerBackend->m_mixDevices ) { if ( md.get() == 0 ) continue; // invalid if ( md->id() == mixdeviceID ) { mdRet = md; break; // found } } return mdRet; } shared_ptr Mixer::getMixdeviceById( const QString& mixdeviceID ) { qCDebug(KMIX_LOG) << "id=" << mixdeviceID << "md=" << _mixerBackend->m_mixDevices.get(mixdeviceID).get()->id(); return _mixerBackend->m_mixDevices.get(mixdeviceID); // shared_ptr md; // int num = _mixerBackend->id2num(mixdeviceID); // if ( num!=-1 && num < (int)size() ) // { // md = (*this)[num]; // } // return md; } /** Call this if you have a *reference* to a Volume object and have modified that locally. Pass the MixDevice associated to that Volume to this method for writing back the changed value to the mixer. Hint: Why do we do it this way? - It is fast (no copying of Volume objects required) - It is easy to understand ( read - modify - commit ) */ void Mixer::commitVolumeChange(shared_ptr md) { _mixerBackend->writeVolumeToHW(md->id(), md); if (md->isEnum()) { _mixerBackend->setEnumIdHW(md->id(), md->enumId()); } if (md->captureVolume().hasSwitch()) { // Make sure to re-read the hardware, because setting capture might have failed. // This is due to exclusive capture groups. // If we wouldn't do this, KMix might show a Capture Switch disabled, but // in reality the capture switch is still on. // // We also cannot rely on a notification from the driver (SocketNotifier), because // nothing has changed, and so there s nothing to notify. _mixerBackend->readSetFromHWforceUpdate(); if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "committing a control with capture volume, that might announce: " << md->id(); _mixerBackend->readSetFromHW(); } if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "committing announces the change of: " << md->id(); // We announce the change we did, so all other parts of KMix can pick up the change ControlManager::instance().announce(md->mixer()->id(), ControlManager::Volume, QString("Mixer.commitVolumeChange()")); } // @dbus, used also in kmix app void Mixer::increaseVolume( const QString& mixdeviceID ) { increaseOrDecreaseVolume(mixdeviceID, false); } // @dbus void Mixer::decreaseVolume( const QString& mixdeviceID ) { increaseOrDecreaseVolume(mixdeviceID, true); } /** * Increase or decrease all playback and capture channels of the given control. * This method is very similar to MDWSlider::increaseOrDecreaseVolume(), but it will * NOT auto-unmute. * * @param mixdeviceID The control name * @param decrease true for decrease. false for increase */ void Mixer::increaseOrDecreaseVolume( const QString& mixdeviceID, bool decrease ) { shared_ptr md= getMixdeviceById( mixdeviceID ); if (md.get() != 0) { Volume& volP=md->playbackVolume(); if ( volP.hasVolume() ) { volP.changeAllVolumes(volP.volumeStep(decrease)); } Volume& volC=md->captureVolume(); if ( volC.hasVolume() ) { volC.changeAllVolumes(volC.volumeStep(decrease)); } _mixerBackend->writeVolumeToHW(mixdeviceID, md); } ControlManager::instance().announce(md->mixer()->id(), ControlManager::Volume, QString("Mixer.increaseOrDecreaseVolume()")); /************************************************************ It is important, not to implement this method like this: int vol=volume(mixdeviceID); setVolume(mixdeviceID, vol-5); It creates too big rounding errors. If you don't believe me, then do a decreaseVolume() and increaseVolume() with "vol.maxVolume() == 31". ***********************************************************/ } void Mixer::setDynamic ( bool dynamic ) { m_dynamic = dynamic; } bool Mixer::isDynamic() { return m_dynamic; } bool Mixer::moveStream( const QString id, const QString& destId ) { // We should really check that id is within our md's.... bool ret = _mixerBackend->moveStream( id, destId ); ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("Mixer.moveStream()")); return ret; } diff --git a/core/mixer.h b/core/mixer.h index 3237d170..ef5cbe0a 100644 --- a/core/mixer.h +++ b/core/mixer.h @@ -1,225 +1,225 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> * 1996-2000 Christian Esken * Sven Fischer * 2002 - Helio Chissini de Castro * * 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 RANDOMPREFIX_MIXER_H #define RANDOMPREFIX_MIXER_H #include #include #include #include "core/volume.h" #include "backends/mixer_backend.h" #include "core/MasterControl.h" #include "mixset.h" #include "core/GlobalConfig.h" #include "core/mixdevice.h" #include "dbus/dbusmixerwrapper.h" #include "kmixcore_export.h" class Volume; class KConfig; class KMIXCORE_EXPORT Mixer : public QObject { Q_OBJECT public: /** * Status for Mixer operations. * * OK_UNCHANGED is a special variant of OK. It must be implemented by * backends that use needsPolling() == true. See Mixer_OSS.cpp for an * example. Rationale is that we need a proper change check: Otherwise * the DBUS Session Bus is massively spammed. Also quite likely the Mixer * GUI might get updated all the time. * */ enum MixerError { OK=0, ERR_PERM=1, ERR_WRITE, ERR_READ, ERR_OPEN, OK_UNCHANGED }; Mixer(const QString &ref_driverName, int device); virtual ~Mixer(); static int numDrivers(); - QString getDriverName(); + QString getDriverName() const; - shared_ptr find(const QString& devPK); + shared_ptr find(const QString& devPK) const; static Mixer* findMixer( const QString& mixer_id); void volumeSave( KConfig *config ); void volumeLoad( KConfig *config ); /// Tells the number of the mixing devices unsigned int size() const; /// Returns a pointer to the mix device with the given number // TODO remove this method. Only used by ViewDockAreaPopup: dockMD = (*mixer)[0]; shared_ptr operator[](int val_i_num); /// Returns a pointer to the mix device whose type matches the value /// given by the parameter and the array MixerDevNames given in /// mixer_oss.cpp (0 is Volume, 4 is PCM, etc.) shared_ptr getMixdeviceById( const QString& deviceID ); /// Open/grab the mixer for further interaction bool openIfValid(); /// Returns whether the card is open/operational bool isOpen() const; /// Close/release the mixer virtual void close(); /// Reads balance int balance() const; /// Returns a detailed state message after errors. Only for diagnostic purposes, no i18n. QString& stateMessage() const; /** * Returns the name of the card/chip/hardware, as given by the driver. The name is NOT instance specific, * so if you install two identical soundcards, two of them will deliver the same mixerName(). * Use this method if you need an instance-UNspecific name, e.g. for finding an appropriate * mixer layout for this card, or as a prefix for constructing instance specific ID's like in id(). */ - virtual QString getBaseName(); + virtual QString getBaseName() const; /// Wrapper to Mixer_Backend QString translateKernelToWhatsthis(const QString &kernelName); /** * Get a name suitable for a human user to read, possibly with quoted ampersand. * The latter is required by some GUI elements like QRadioButton or when used as a * tab label, as '&' introduces an accelerator there. * * @param ampersandQuoted @c true if '&' characters are to be quoted * @return the readable device name */ - QString readableName(bool ampersandQuoted = false); + QString readableName(bool ampersandQuoted = false) const; // Returns the name of the driver, e.g. "OSS" or "ALSA0.9" static QString driverName(int num); static bool getBeepOnVolumeChange() { GlobalConfigData& gcd = GlobalConfig::instance().data; return gcd.beepOnVolumeChange; } /** * Returns an unique ID of the Mixer. It currently looks like ":::" */ - QString& id(); + const QString &id() const; int getCardInstance() const { return _mixerBackend->getCardInstance(); } /// Returns an Universal Device Identification of the Mixer. This is an ID that relates to the underlying operating system. // For OSS and ALSA this is taken from Solid (actually HAL). For Solaris this is just the device name. // Examples: // ALSA: /org/freedesktop/Hal/devices/usb_device_d8c_1_noserial_if0_sound_card_0_2_alsa_control__1 // OSS: /org/freedesktop/Hal/devices/usb_device_d8c_1_noserial_if0_sound_card_0_2_oss_mixer__1 // Solaris: /dev/audio - QString& udi(); + const QString &udi() const; // Returns a DBus path for this mixer // Used also by MixDevice to bind to this path const QString dbusPath(); static QList & mixers(); /****************************************** The KMix GLOBAL master card. Please note that KMix and KMixPanelApplet can have a different MasterCard's at the moment (but actually KMixPanelApplet does not read/save this yet). At the moment it is only used for selecting the Mixer to use in KMix's DockIcon. ******************************************/ static void setGlobalMaster(QString ref_card, QString ref_control, bool preferred); static shared_ptr getGlobalMasterMD(); static shared_ptr getGlobalMasterMD(bool fallbackAllowed); static Mixer* getGlobalMasterMixer(); static Mixer* getGlobalMasterMixerNoFalback(); static MasterControl& getGlobalMasterPreferred(bool fallbackAllowed = true); QString getRecommendedDeviceId(); /****************************************** The recommended master of this Mixer. ******************************************/ - shared_ptr getLocalMasterMD(); + shared_ptr getLocalMasterMD() const; void setLocalMasterMD(QString&); /// get the actual MixSet MixSet& getMixSet(); /// DBUS oriented methods virtual void increaseVolume( const QString& mixdeviceID ); virtual void decreaseVolume( const QString& mixdeviceID ); /// Says if we are dynamic (e.g. widgets can come and go) virtual void setDynamic( bool dynamic = true ); virtual bool isDynamic(); static bool dynamicBackendsPresent(); static bool pulseaudioPresent(); virtual bool moveStream( const QString id, const QString& destId ); virtual int mediaPlay(QString id) { return _mixerBackend->mediaPlay(id); }; virtual int mediaPrev(QString id) { return _mixerBackend->mediaPrev(id); }; virtual int mediaNext(QString id) { return _mixerBackend->mediaNext(id); }; void commitVolumeChange( shared_ptr md ); public slots: void readSetFromHWforceUpdate() const; virtual void setBalance(int balance); // sets the m_balance (see there) signals: void newBalance(Volume& ); void controlChanged(void); // TODO remove? protected: int m_balance; // from -100 (just left) to 100 (just right) static QList s_mixers; private: void setBalanceInternal(Volume& vol); void recreateId(); void increaseOrDecreaseVolume( const QString& mixdeviceID, bool decrease ); Mixer_Backend *_mixerBackend; QString _id; QString _masterDevicePK; static MasterControl _globalMasterCurrent; static MasterControl _globalMasterPreferred; bool m_dynamic; static bool m_beepOnVolumeChange; }; #endif diff --git a/gui/dialogaddview.cpp b/gui/dialogaddview.cpp index 330207ef..0fbab838 100644 --- a/gui/dialogaddview.cpp +++ b/gui/dialogaddview.cpp @@ -1,222 +1,227 @@ /* * 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/dialogaddview.h" #include #include #include #include #include #include #include "core/mixdevice.h" #include "core/mixer.h" static QStringList viewNames; static QStringList viewIds; DialogAddView::DialogAddView(QWidget *parent, Mixer *mixer) : DialogBase( parent ) { // TODO 000 Adding View for MPRIS2 is broken. We need at least a dummy XML GUI Profile. Also the // fixed list below is plain wrong. Actually we should get the Profile list from either the XML files or // from the backend. The latter is probably easier for now. if ( viewNames.isEmpty() ) { // initialize static list. Later this list could be generated from the actually installed profiles viewNames.append(i18n("All controls")); viewNames.append(i18n("Only playback controls")); viewNames.append(i18n("Only capture controls")); viewIds.append("default"); viewIds.append("playback"); viewIds.append("capture"); } setWindowTitle( i18n( "Add View" ) ); if ( Mixer::mixers().count() > 0 ) setButtons( QDialogButtonBox::Ok|QDialogButtonBox::Cancel ); else { setButtons( QDialogButtonBox::Cancel ); } m_listForChannelSelector = nullptr; createWidgets(mixer); // Open with Mixer Hardware #0 } /** * Create basic widgets of the Dialog. */ void DialogAddView::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->setSpacing(DialogBase::horizontalSpacing()); QLabel *qlbl = new QLabel( i18n("Select 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; iaddItem( mixer->readableName() ); + const Mixer *mixer = (Mixer::mixers())[i]; + const shared_ptr md = mixer->getLocalMasterMD(); + const QString iconName = (md!=nullptr) ? md->iconName() : "media-playback-start"; + m_cMixer->addItem(QIcon::fromTheme(iconName), mixer->readableName() ); } // end for all_Mixers // Make the current Mixer the current item in the ComboBox int findIndex = m_cMixer->findText( ptr_mixer->readableName() ); if ( findIndex != -1 ) m_cMixer->setCurrentIndex( findIndex ); m_cMixer->setToolTip( i18n("Current mixer" ) ); mixerNameLayout->addWidget(m_cMixer); } // end if (more_than_1_Mixer) if ( Mixer::mixers().count() > 0 ) { QLabel *qlbl = new QLabel( i18n("Select the design for the new view:"), mainFrame ); layout->addWidget(qlbl); createPage(Mixer::mixers()[0]); 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 DialogAddView::createPageByID(int mixerId) { //qCDebug(KMIX_LOG) << "DialogAddView::createPage()"; QString selectedMixerName = m_cMixer->itemText(mixerId); for( int i =0; ireadableName() == selectedMixerName ) { createPage(mixer); break; } } // for } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogAddView::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 m_listForChannelSelector; setButtonEnabled(QDialogButtonBox::Ok, false); /** Reset page end -------------------------------------------------- */ QWidget *mainFrame = mainWidget(); QVBoxLayout *layout = qobject_cast(mainFrame->layout()); Q_ASSERT(layout!=nullptr); m_listForChannelSelector = new QListWidget(mainFrame); m_listForChannelSelector->setUniformItemSizes(true); m_listForChannelSelector->setSelectionMode(QAbstractItemView::SingleSelection); connect(m_listForChannelSelector, SIGNAL(itemSelectionChanged()), this, SLOT(profileSelectionChanged())); layout->addWidget(m_listForChannelSelector); - for( int i=0; iisDynamic()) { // TODO: The mixer's backend MUST be inspected to find out the supported profiles. // Hardcoding it here is only a quick workaround. continue; } // Create an item for each view type QString name = viewNames.at(i); QListWidgetItem *item = new QListWidgetItem(m_listForChannelSelector); item->setText(name); item->setData(Qt::UserRole, viewIds.at(i)); // mixer ID as data } + + // If there is only one option available to select, then preselect it. + if (m_listForChannelSelector->count()==1) m_listForChannelSelector->setCurrentRow(0); } void DialogAddView::profileSelectionChanged() { QList items = m_listForChannelSelector->selectedItems(); setButtonEnabled(QDialogButtonBox::Ok, !items.isEmpty()); } void DialogAddView::apply() { Mixer *mixer = nullptr; if ( Mixer::mixers().count() == 1 ) { // only one mixer => 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 QString selectedMixerName = m_cMixer->itemText(m_cMixer->currentIndex()); for( int i =0; ireadableName() == selectedMixerName ) { mixer = (Mixer::mixers())[i]; break; } } // for } Q_ASSERT(mixer!=nullptr); QList items = m_listForChannelSelector->selectedItems(); if (items.isEmpty()) return; // nothing selected QListWidgetItem *item = items.first(); QString viewName = item->data(Qt::UserRole).toString(); qCDebug(KMIX_LOG) << "We should now create a new view " << viewName << " for mixer " << mixer->id(); resultMixerId = mixer->id(); resultViewName = viewName; } diff --git a/gui/dialogselectmaster.cpp b/gui/dialogselectmaster.cpp index 57f7877c..5c7db5ca 100644 --- a/gui/dialogselectmaster.cpp +++ b/gui/dialogselectmaster.cpp @@ -1,229 +1,232 @@ /* * 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; iaddItem( mixer->readableName(), mixer->id() ); + const Mixer *mixer = (Mixer::mixers())[i]; + const shared_ptr 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()->loadIcon("audio-volume-high", KIconLoader::Small, IconSize(KIconLoader::Small)); QListWidgetItem *item = new QListWidgetItem(icon, 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()->loadIcon(md->iconName(), KIconLoader::Small, IconSize(KIconLoader::Small)); QListWidgetItem *item = new QListWidgetItem(icon, 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 72563d99..90aa8e29 100644 --- a/gui/dialogviewconfiguration.cpp +++ b/gui/dialogviewconfiguration.cpp @@ -1,457 +1,455 @@ /* * 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" DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent) : QListWidgetItem(parent) { qCDebug(KMIX_LOG) << "DialogViewConfigurationItem() default constructor"; refreshItem(); } DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, QString id, bool shown, QString name, int splitted, const QString& iconName) : QListWidgetItem(parent), _id(id), _shown(shown), _name(name), _splitted(splitted), _iconName(iconName) { refreshItem(); } void DialogViewConfigurationItem::refreshItem() { setFlags((flags() | Qt::ItemIsDragEnabled) & ~Qt::ItemIsDropEnabled); setText(_name); setIcon(KIconLoader::global()->loadIcon( _iconName, KIconLoader::Small, IconSize(KIconLoader::Toolbar) )); setData(Qt::ToolTipRole, _id); // a hack. I am giving up to do it right setData(Qt::DisplayRole, _name); } /** * Serializer. Used for DnD. */ static QDataStream & operator<< ( QDataStream & s, const DialogViewConfigurationItem & item ) { s << item._id; s << item._shown; s << item._name; s << item._splitted; s << item._iconName; //qCDebug(KMIX_LOG) << "<< serialize << " << s; return s; } /** * Deserializer. Used for DnD. */ static QDataStream & operator>> ( QDataStream & s, DialogViewConfigurationItem & item ) { QString id; s >> id; item._id = id; bool shown; s >> shown; item._shown = shown; QString name; s >> name; item._name = name; int splitted; s >> splitted; item._splitted = splitted; QString iconName; s >> iconName; item._iconName = iconName; //qCDebug(KMIX_LOG) << ">> deserialize >> " << id << name << 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(); DialogViewConfigurationItem* item = 0; QByteArray data; { QDataStream stream(&data, QIODevice::WriteOnly); // we only support single selection 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); const bool sourceIsActiveList = mimeData->data("application/x-kde-source-treewidget") == "active"; DialogViewConfigurationItem* item = new DialogViewConfigurationItem(0); // needs parent, use this temporarily stream >> *item; item->refreshItem(); emit dropped(this, index, item, sourceIsActiveList); 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); _qlw = 0; _qlwInactive = 0; createPage(); } /** * Drop an item from one list to the other */ void DialogViewConfiguration::slotDropped ( DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList ) { //qCDebug(KMIX_LOG) << "dropped item (index" << index << "): " << item->_id << item->_shown << item->_name << item->_splitted << item->_iconName; if ( list == _qlw ) { //DialogViewConfigurationItem* after = index > 0 ? static_cast(list->item(index-1)) : 0; //qCDebug(KMIX_LOG) << "after" << after->text() << after->internalTag(); if ( sourceIsActiveList ) { // has been dragged within the active list (moved). _qlw->insertItem ( index, item ); //moveActive(item, after); } else { // dragged from the inactive list to the active list _qlw->insertItem ( index, item ); //insertActive(item, after, true); } } else if ( list == _qlwInactive ) { // has been dragged to the inactive list -> remove from the active list. //removeActive(item); _qlwInactive->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) { foreach ( QListWidgetItem* item, from->selectedItems() ) { QListWidgetItem *clonedItem = item->clone(); to->addItem ( clonedItem ); to->setCurrentItem(clonedItem); delete item; } } void DialogViewConfiguration::moveSelectionToActiveList() { moveSelection(_qlwInactive, _qlw); } void DialogViewConfiguration::moveSelectionToInactiveList() { moveSelection(_qlw, _qlwInactive); } void DialogViewConfiguration::selectionChangedActive() { // bool activeIsNotEmpty = _qlw->selectedItems().isEmpty(); moveRightButton->setEnabled(! _qlw->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() { - QList &mdws = _view._mdws; - 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, SIGNAL(dropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool)), this , SLOT(slotDropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool))); 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, SIGNAL(clicked(bool)), SLOT(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, SIGNAL(clicked(bool)), SLOT(moveSelectionToInactiveList())); addSpacer(1,5); _qlw = new DialogViewConfigurationWidget(frame); _glayout->addWidget(_qlw,1,0); connect(_qlw , SIGNAL(dropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool)), this , SLOT(slotDropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool))); // --- CONTROLS IN THE GRID ------------------------------------ //QPalette::ColorRole bgRole; - for ( int i=0; i(_view.mixDeviceAt(i)); + if (mdw==nullptr) continue; + //if ( i%2 == 0) bgRole = QPalette::Base; else bgRole = QPalette::AlternateBase; - QWidget *qw = mdws[i]; - if ( qw->inherits("MixDeviceWidget") ) { - MixDeviceWidget *mdw = static_cast(qw); shared_ptr md = mdw->mixDevice(); - QString mdName = md->readableName(); + 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; if ( mdw->isVisible() ) { new DialogViewConfigurationItem(_qlw, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); } else { new DialogViewConfigurationItem(_qlwInactive, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); } /* if ( ! md->isEnum() && ( ( md->playbackVolume().count() > 1) || ( md->captureVolume().count() > 1) ) ) { cb = new QCheckBox( "", vboxForScrollView ); // split cb->setBackgroundRole(bgRole); cb->setAutoFillBackground(true); _qSplitCB.append(cb); cb->setChecked( ! mdw->isStereoLinked() ); grid->addWidget(cb,1+i,1); } else { _qSplitCB.append(0); } */ /* if ( ! md->isEnum() && ( md->playbackVolume().count() + md->captureVolume().count() >0 ) ) { cb = new QCheckBox( "", vboxForScrollView ); // limit cb->setBackgroundRole(bgRole); cb->setAutoFillBackground(true); _qLimitCB.append(cb); grid->addWidget(cb,1+i,2); } else { */ //_qLimitCB.append(0); /*}*/ - } // is not enum } // for all MDW's connect(_qlwInactive, SIGNAL(itemSelectionChanged()), this , SLOT(selectionChangedInactive())); connect(_qlw, SIGNAL(itemSelectionChanged()), this , SLOT(selectionChangedActive())); // scrollArea->updateGeometry(); updateGeometry(); connect(this, SIGNAL(accepted()), this, SLOT(apply())); #ifndef QT_NO_ACCESSIBILITY moveLeftButton->setAccessibleName( i18n("Show the selected channel") ); moveRightButton->setAccessibleName( i18n("Hide the selected channel") ); _qlw->setAccessibleName( i18n("Visible channels") ); _qlwInactive->setAccessibleName( i18n("Available channels") ); #endif } DialogViewConfiguration::~DialogViewConfiguration() { } void DialogViewConfiguration::apply() { // --- We have a 3-Step Apply of the Changes ------------------------------- // -1- Update view and profile ***************************************** GUIProfile* prof = _view.guiProfile(); - GUIProfile::ControlSet& oldControlset = prof->getControls(); + const GUIProfile::ControlSet &oldControlset = prof->getControls(); GUIProfile::ControlSet newControlset; QAbstractItemModel* model; model = _qlw->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->setVisible(GuiVisibility::GuiNEVER); + 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, GUIProfile::ControlSet& oldCtlSet, GUIProfile::ControlSet& newCtlSet) +void DialogViewConfiguration::prepareControls(QAbstractItemModel* model, bool isActiveView, const GUIProfile::ControlSet &oldCtlSet, GUIProfile::ControlSet &newCtlSet) { - int numRows = model->rowCount(); + 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 ************************** - foreach ( QWidget *qw, _view._mdws ) + for (int i = 0; i(qw); - if ( !mdw ) { - continue; - } + MixDeviceWidget *mdw = qobject_cast(_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); + QRegExp idRegexp(control->id()); if ( ctlId.contains(idRegexp) ) { // found. Create a copy ProfControl* newCtl = new ProfControl(*control); - newCtl->id = '^' + ctlId + '$'; // Replace the (possible generic) regexp by the actual ID + 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 e58cd982..078e4b94 100644 --- a/gui/dialogviewconfiguration.h +++ b/gui/dialogviewconfiguration.h @@ -1,137 +1,137 @@ //-*-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 { friend class QDataStream; public: explicit DialogViewConfigurationItem( QListWidget *parent); DialogViewConfigurationItem( QListWidget *parent, QString id, bool shown, QString name, int splitted, const QString& iconName ); void refreshItem(); public: QString _id; bool _shown; QString _name; int _splitted; QString _iconName; }; 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, bool sourceIsActiveList); 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); ~DialogViewConfiguration(); public slots: void apply(); private slots: void slotDropped(DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList ); void moveSelectionToActiveList(); void moveSelectionToInactiveList(); void selectionChangedActive(); void selectionChangedInactive(); private: //void dragEnterEvent(QDragEnterEvent *event); - void prepareControls(QAbstractItemModel* model, bool isActiveView, GUIProfile::ControlSet& oldCtlSet, GUIProfile::ControlSet& newCtlSet); + 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 *_qlw; DialogViewConfigurationWidget *_qlwInactive; }; #endif diff --git a/gui/guiprofile.cpp b/gui/guiprofile.cpp index 7999ce5f..7e12c0e8 100644 --- a/gui/guiprofile.cpp +++ b/gui/guiprofile.cpp @@ -1,932 +1,876 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 2006-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/guiprofile.h" // Qt -#include -#include -#include +#include +#include +#include #include -// System -#include -#include - // KMix #include "core/mixer.h" -QMap GUIProfile::s_profiles; -GuiVisibility const GuiVisibility::GuiSIMPLE (QString("simple" ) , GuiVisibility::SIMPLE); -GuiVisibility const GuiVisibility::GuiEXTENDED(QString("extended") , GuiVisibility::EXTENDED); -// For backwards compatibility, GuiFULL has the ID "all", and not "full" -GuiVisibility const GuiVisibility::GuiFULL (QString("all" ) , GuiVisibility::FULL); -GuiVisibility const GuiVisibility::GuiCUSTOM (QString("custom" ) , GuiVisibility::CUSTOM); -GuiVisibility const GuiVisibility::GuiNEVER (QString("never" ) , GuiVisibility::NEVER); +QMap s_profiles; -bool SortedStringComparator::operator()(const std::string& s1, const std::string& s2) const { - return ( s1 < s2 ); +static QString visibilityToString(GuiVisibility vis) +{ + switch (vis) + { +case GuiVisibility::Simple: return ("simple"); +case GuiVisibility::Extended: return ("extended"); +// For backwards compatibility, 'Full' has the ID "all" and not "full" +case GuiVisibility::Full: return ("all"); +case GuiVisibility::Custom: return ("custom"); +case GuiVisibility::Never: return ("never"); +case GuiVisibility::Default: return ("default"); +default: return ("unknown"); + } } + +static GuiVisibility visibilityFromString(const QString &str) +{ + if (str=="simple") return (GuiVisibility::Simple); + else if (str=="extended") return (GuiVisibility::Extended); + else if (str=="all") return (GuiVisibility::Full); + else if (str=="custom") return (GuiVisibility::Custom); + else if (str=="never") return (GuiVisibility::Never); + + qCWarning(KMIX_LOG) << "Unknown string value" << str; + return (GuiVisibility::Full); +} + + /** * Product comparator for sorting: * We want the comparator to sort ascending by Vendor. "Inside" the Vendors, we sort by Product Name. */ bool ProductComparator::operator()(const ProfProduct* p1, const ProfProduct* p2) const { if ( p1->vendor < p2->vendor ) { return ( true ); } else if ( p1->vendor > p2->vendor ) { return ( false ); } else if ( p1->productName < p2->productName ) { return ( true ); } else if ( p1->productName > p2->productName ) { return ( false ); } else { /** * We reach this point, if vendor and product name is identical. * Actually we don't care about the order then, so we decide that "p1" comes first. * * (Hint: As this is a set comparator, the return value HERE doesn't matter that * much. But if we would decide later to change this Comparator to be a Map Comparator, * we must NOT return a "0" for identity - this would lead to non-insertion on insert()) */ return true; } } GUIProfile::GUIProfile() { _dirty = false; _driverVersionMin = 0; _driverVersionMax = 0; _generation = 1; } GUIProfile::~GUIProfile() { qCWarning(KMIX_LOG) << "Thou shalt not delete any GUI profile. This message is only OK, when quitting KMix"; qDeleteAll(_controls); qDeleteAll(_products); } /** * Clears the GUIProfile cache. You must only call this * before termination of the application, as GUIProfile instances are used in other classes, especially the views. * There is no need to call this in non-GUI applications like kmixd and kmixctrl. */ void GUIProfile::clearCache() { qDeleteAll(s_profiles); s_profiles.clear(); } - - -void GUIProfile::setId(const QString& id) -{ - _id = id; -} - -QString GUIProfile::getId() const -{ - return _id; -} - -bool GUIProfile::isDirty() const { - return _dirty; -} - -void GUIProfile::setDirty() { - _dirty = true; -} - /** * Build a profile name. Suitable to use as primary key and to build filenames. * @arg mixer The mixer * @arg profileName The profile name (e.g. "capture", "playback", "my-cool-profile", or "any" * @return The profile name */ -QString GUIProfile::buildProfileName(Mixer* mixer, QString profileName, bool ignoreCard) +static QString buildProfileName(const Mixer *mixer, const QString &profileName, bool ignoreCard) { QString fname; fname += mixer->getDriverName(); if (!ignoreCard) { fname += ".%1.%2"; fname = fname.arg(mixer->getBaseName()).arg(mixer->getCardInstance()); } fname += '.' + profileName; fname.replace(' ','_'); return fname; } + /** * Generate a readable profile name (for presenting to the user). * Hint: Currently used as Tab label. */ -QString GUIProfile::buildReadableProfileName(Mixer* mixer, QString profileName) +static QString buildReadableProfileName(const Mixer *mixer, const QString &profileName) { QString fname; fname += mixer->getBaseName(); if ( mixer->getCardInstance() > 1 ) { fname += " %1"; fname = fname.arg(mixer->getCardInstance()); } if ( profileName != "default" ) { fname += ' ' + profileName; } qCDebug(KMIX_LOG) << fname; return fname; } /** * Returns the GUIProfile for the given ID (= "fullyQualifiedName"). * If not found 0 is returned. There is no try to load it. * * @returns The loaded GUIProfile for the given ID */ -GUIProfile* GUIProfile::find(QString id) +GUIProfile *GUIProfile::find(const QString &id) { - // Not thread safe (due to non-atomic contains()/get() - if ( s_profiles.contains(id) ) - { - return s_profiles[id]; - } - else - { - return 0; - } + // Return found value or default-constructed one (nullptr). + // Does not insert into map. Now thread-safe. + return (s_profiles.value(id)); +} + + +static QString createNormalizedFilename(const QString &profileId) +{ + QString profileIdNormalized(profileId); + profileIdNormalized.replace(':', '.'); + + QString fileName("profiles/"); + fileName = fileName + profileIdNormalized + ".xml"; + return fileName; +} + + +/** + * Loads a GUI Profile from disk (XML profile file). + * It tries to load the Soundcard specific file first (a). + * If it doesn't exist, it will load the default profile corresponding to the soundcard driver (b). + */ +static GUIProfile *loadProfileFromXMLfiles(const Mixer *mixer, const QString &profileName) +{ + GUIProfile* guiprof = nullptr; + QString fileName = createNormalizedFilename(profileName); + QString fileNameFQ = QStandardPaths::locate(QStandardPaths::DataLocation, fileName ); + + if ( ! fileNameFQ.isEmpty() ) { + guiprof = new GUIProfile(); + if ( guiprof->readProfile(fileNameFQ) && ( guiprof->match(mixer) > 0) ) { + // loaded + } + else { + delete guiprof; // not good (e.g. Parsing error => drop this profile silently) + guiprof = nullptr; + } + } + else { + qCDebug(KMIX_LOG) << "Ignore file " <getId()] = guiprof; + qCDebug(KMIX_LOG) << "I have added" << guiprof->getId() << "; Number of profiles is now " << s_profiles.size() ; } /** * Finds the correct profile for the given mixer. * If already loaded from disk, returns the cached version. * Otherwise load profile from disk: Priority: Card specific profile, Card unspecific profile * * @arg mixer The mixer * @arg profileName The profile name (e.g. "ALSA.X-Fi.default", or "OSS.intel-cha51.playback") * A special case is "", which means that a card specific name should be generated. * @arg profileNameIsFullyQualified If true, an exact match will be searched. Otherwise it is a simple name like "playback" or "capture" * @arg ignoreCardName If profileName not fully qualified, this is used in building the requestedProfileName * @return GUIProfile* The loaded GUIProfile, or 0 if no profile matched. Hint: if you use allowFallback==true, this should never return 0. */ -GUIProfile* GUIProfile::find(Mixer* mixer, QString profileName, bool profileNameIsFullyQualified, bool ignoreCardName) +GUIProfile *GUIProfile::find(const Mixer *mixer, const QString &profileName, bool profileNameIsFullyQualified, bool ignoreCardName) { - GUIProfile* guiprof = 0; + GUIProfile *guiprof = nullptr; - if ( mixer == 0 || profileName.isEmpty() ) - return 0; + if (mixer==nullptr || profileName.isEmpty()) return (nullptr); // if ( mixer->isDynamic() ) { // qCDebug(KMIX_LOG) << "GUIProfile::find() Not loading GUIProfile for Dynamic Mixer (e.g. PulseAudio)"; // return 0; // } QString requestedProfileName; QString fullQualifiedProfileName; if ( profileNameIsFullyQualified ) { requestedProfileName = profileName; fullQualifiedProfileName = profileName; } else { requestedProfileName = buildProfileName(mixer, profileName, ignoreCardName); fullQualifiedProfileName = buildProfileName(mixer, profileName, false); } if ( s_profiles.contains(fullQualifiedProfileName) ) { guiprof = s_profiles.value(fullQualifiedProfileName); // Cached } else { guiprof = loadProfileFromXMLfiles(mixer, requestedProfileName); // Load from XML ###Card specific profile### - if ( guiprof != 0 ) { + if ( guiprof!=nullptr) { guiprof->_mixerId = mixer->id(); guiprof->setId(fullQualifiedProfileName); // this one contains some soundcard id (basename + instance) if ( guiprof->getName().isEmpty() ) { // If the profile didn't contain a name then lets define one guiprof->setName(buildReadableProfileName(mixer,profileName)); // The caller can rename this if he likes guiprof->setDirty(); } if ( requestedProfileName != fullQualifiedProfileName) { // This is very important! // When the final profileName (fullQualifiedProfileName) is different from // what we have loaded (requestedProfileName, e.g. "default"), we MUST // set the profile dirty, so it gets saved. Otherwise we would write the // fullQualifiedProfileName in the kmixrc, and will not find it on the next // start of KMix. guiprof->setDirty(); } addProfile(guiprof); } } - return guiprof; -} - -/* - * Add the profile to the internal list of profiles (Profile caching). - */ -void GUIProfile::addProfile(GUIProfile* guiprof) -{ - // Possible TODO: Delete old mapped GUIProfile, if it exists. Otherwise we might leak one GUIProfile instance - // per unplug/plug sequence. Its quite likely possible that currently no Backend leads to a - // leak: This is because they either don't hotplug cards (PulseAudio, MPRIS2), or they ship - // a XML gui profile (so the Cached version is retrieved, and addProfile() is not called). - - s_profiles[guiprof->getId()] = guiprof; - qCDebug(KMIX_LOG) << "I have added" << guiprof->getId() << "; Number of profiles is now " << s_profiles.size() ; + return (guiprof); } - - -/** - * Loads a GUI Profile from disk (xml profile file). - * It tries to load the Soundcard specific file first (a). - * If it doesn't exist, it will load the default profile corresponding to the soundcard driver (b). - */ -GUIProfile* GUIProfile::loadProfileFromXMLfiles(Mixer* mixer, QString profileName) -{ - GUIProfile* guiprof = 0; - QString fileName = createNormalizedFilename(profileName); - QString fileNameFQ = QStandardPaths::locate(QStandardPaths::DataLocation, fileName ); - - if ( ! fileNameFQ.isEmpty() ) { - guiprof = new GUIProfile(); - if ( guiprof->readProfile(fileNameFQ) && ( guiprof->match(mixer) > 0) ) { - // loaded - } - else { - delete guiprof; // not good (e.g. Parsing error => drop this profile silently) - guiprof = 0; - } - } - else { - qCDebug(KMIX_LOG) << "Ignore file " <vendor = mixer->getDriverName(); prd->productName = mixer->readableName(); prd->productRelease = "1.0"; fallback->_products.insert(prd); static QString matchAll(".*"); static QString matchAllSctl(".*"); ProfControl* ctl = new ProfControl(matchAll, matchAllSctl); //ctl->regexp = matchAll; // make sure id matches the regexp ctl->setMandatory(true); fallback->_controls.push_back(ctl); fallback->_soundcardDriver = mixer->getDriverName(); fallback->_soundcardName = mixer->readableName(); fallback->finalizeProfile(); fallback->_mixerId = mixer->id(); fallback->setId(fullQualifiedProfileName); // this one contains some soundcard id (basename + instance) fallback->setName(buildReadableProfileName(mixer, QString("default"))); // The caller can rename this if he likes fallback->setDirty(); /* -3- Add the profile to the static list * Hint: This looks like a memory leak, as we never remove profiles from memory while KMix runs. * Especially with application streams it looks suspicious. But please be aware that this method is only * called for soundcard hotplugs, and not on stream hotplugs. At least it is supposed to be like that. * * Please also see the docs at addProfile(), they also address the possible memory leakage. */ addProfile(fallback); return fallback; } /** * Fill the profile with the data from the given XML profile file. * @par ref_fileName: Full qualified filename (with path). * @return bool True, if the profile was successfully created. False if not (e.g. parsing error). */ -bool GUIProfile::readProfile(const QString& ref_fileName) +bool GUIProfile::readProfile(const QString &ref_fileName) { - QXmlSimpleReader *xmlReader = new QXmlSimpleReader(); - qCDebug(KMIX_LOG) << "Read profile:" << ref_fileName ; - QFile xmlFile( ref_fileName ); - QXmlInputSource source( &xmlFile ); - GUIProfileParser* gpp = new GUIProfileParser(this); - xmlReader->setContentHandler(gpp); - bool ok = xmlReader->parse( source ); + QXmlSimpleReader xmlReader; + qCDebug(KMIX_LOG) << "Read profile" << ref_fileName; + QFile xmlFile(ref_fileName); + QXmlInputSource source(&xmlFile); + GUIProfileParser gpp(this); + xmlReader.setContentHandler(&gpp); + bool ok = xmlReader.parse(source); //std::cout << "Raw Profile: " << *this; if ( ok ) { ok = finalizeProfile(); } // Read OK else { // !! this error message about faulty profiles should probably be surrounded with i18n() - qCCritical(KMIX_LOG) << "ERROR: The profile '" << ref_fileName<< "' contains errors, and is not used."; + qCCritical(KMIX_LOG) << "ERROR: The profile" << ref_fileName<< "contains errors, and cannot be used"; } - delete gpp; - delete xmlReader; - + return ok; } -const QString GUIProfile::createNormalizedFilename(const QString& profileId) -{ - QString profileIdNormalized(profileId); - profileIdNormalized.replace(':', '.'); - - QString fileName("profiles/"); - fileName = fileName + profileIdNormalized + ".xml"; - return fileName; - } bool GUIProfile::writeProfile() { - bool ret = false; QString profileId = getId(); QString fileName = createNormalizedFilename(profileId); QString fileNameFQ = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + '/' + fileName; - qCDebug(KMIX_LOG) << "Write profile:" << fileNameFQ ; - QFile f(fileNameFQ); - if ( f.open(QIODevice::WriteOnly | QFile::Truncate) ) - { - QTextStream out(&f); - out << *this; - f.close(); - ret = true; + qCDebug(KMIX_LOG) << "Write profile" << fileNameFQ; + QSaveFile f(fileNameFQ); + if (!f.open(QIODevice::WriteOnly|QFile::Truncate)) + { + qCWarning(KMIX_LOG) << "Cannot save profile to" << fileNameFQ; + return (false); } - if ( ret ) { - _dirty = false; + QXmlStreamWriter writer(&f); + writer.setAutoFormatting(true); + + // + writer.writeStartDocument(); + + // + writer.writeEndElement(); + + foreach (const ProfProduct *prd, qAsConst(_products)) + { + // vendor + writer.writeAttribute("vendor", prd->vendor); + // name= prd->productName + writer.writeAttribute("name", prd->productName); + // release= prd->productRelease + if (!prd->productRelease.isNull()) writer.writeAttribute("release", prd->productRelease); + // comment= prd->comment + if (!prd->comment.isNull()) writer.writeAttribute("comment", prd->comment); + // /> + writer.writeEndElement(); + } // for all products + + foreach (const ProfControl *profControl, qAsConst(getControls())) + { + // id() + writer.writeAttribute("id", profControl->id()); + // name= profControl->name() + const QString name = profControl->name(); + if (!name.isNull() && name!=profControl->id()) writer.writeAttribute("name", name); + // subcontrols= profControl->renderSubcontrols() + writer.writeAttribute("subcontrols", profControl->renderSubcontrols()); + // show= visibilityToString(profControl->getVisibility()) + writer.writeAttribute("show", visibilityToString(profControl->getVisibility())); + // mandatory= "true" + if (profControl->isMandatory()) writer.writeAttribute("mandatory", "true"); + // split= "true" + if (profControl->isSplit()) writer.writeAttribute("split", "true"); + // /> + writer.writeEndElement(); + } // for all controls + + // + writer.writeEndElement(); + + writer.writeEndDocument(); + if (writer.hasError()) + { + qCWarning(KMIX_LOG) << "XML writing failed to" << fileNameFQ; + f.cancelWriting(); + return (false); } - return ret; + + f.commit(); + _dirty = false; + return (true); } + /** This is now empty. It can be removed */ bool GUIProfile::finalizeProfile() const { - bool ok = true; - return ok; + return (true); } // ------------------------------------------------------------------------------------- void GUIProfile::setControls(ControlSet& newControlSet) { qDeleteAll(_controls); _controls = newControlSet; } -const GUIProfile::ControlSet& GUIProfile::getControls() const -{ - return _controls; -} - -GUIProfile::ControlSet& GUIProfile::getControls() -{ - return _controls; -} - -void GUIProfile::addProduct(ProfProduct* prd) -{ - _products.insert(prd); -} - // ------------------------------------------------------------------------------------- /** * Returns how good the given Mixer matches this GUIProfile. * A value between 0 (not matching at all) and MAXLONG (perfect match) is returned. * * Here is the current algorithm: * * If the driver doesn't match, 0 is returned. (OK) * If the card-name ... (OK) * is "*", this is worth 1 point * doesn't match, 0 is returned. * matches, this is worth 500 points. * * If the "card type" ... * is empty, this is worth 0 points. !!! not implemented yet * doesn't match, 0 is returned. !!! not implemented yet * matches , this is worth 500 points. !!! not implemented yet * * If the "driver version" doesn't match, 0 is returned. !!! not implemented yet * If the "driver version" matches, this is worth ... * 4000 unlimited <=> "*:*" * 6000 toLower-bound-limited <=> "toLower-bound:*" * 6000 upper-bound-limited <=> "*:upper-bound" * 8000 upper- and toLower-bound limited <=> "toLower-bound:upper-bound" * or 10000 points (upper-bound=toLower-bound=bound <=> "bound:bound" * * The Profile-Generation is added to the already achieved points. (done) * The maximum gain is 900 points. * Thus you can create up to 900 generations (0-899) without "overriding" * the points gained from the "driver version" or "card-type". * * For example: card-name="*" (1), card-type matches (1000), * driver version "*:*" (4000), Profile-Generation 4 (4). * Sum: 1 + 1000 + 4000 + 4 = 5004 * * @todo Implement "card type" match value * @todo Implement "version" match value (must be in backends as well) */ -unsigned long GUIProfile::match(Mixer* mixer) { +unsigned long GUIProfile::match(const Mixer *mixer) const +{ unsigned long matchValue = 0; if ( _soundcardDriver != mixer->getDriverName() ) { return 0; } if ( _soundcardName == "*" ) { matchValue += 1; } else if ( _soundcardName != mixer->getBaseName() ) { return 0; // card name does not match } else { matchValue += 500; // card name matches } // !!! we don't check current for the driver version. // So we assign simply 4000 points for now. matchValue += 4000; if ( _generation < 900 ) { matchValue += _generation; } else { matchValue += 900; } return matchValue; } -QString xmlify(QString raw); -QString xmlify(QString raw) +ProfControl::ProfControl(const QString &id, const QString &subcontrols) + : _id(id), + _visibility(GuiVisibility::Simple), + _mandatory(false), + _split(false) { -// qCDebug(KMIX_LOG) << "Before: " << raw; - raw = raw.replace('&', "&"); - raw = raw.replace('<', "<"); - raw = raw.replace('>', ">"); - raw = raw.replace('\'', "'"); - raw = raw.replace('\"', """); -// qCDebug(KMIX_LOG) << "After : " << raw; - return raw; -} - - -QTextStream& operator<<(QTextStream &os, const GUIProfile& guiprof) -{ -// qCDebug(KMIX_LOG) << "ENTER QTextStream& operator<<"; - os << ""; - os << endl << endl; - - os << "" << endl << endl ; - - os << "" << endl; - - for ( GUIProfile::ProductSet::const_iterator it = guiprof._products.begin(); it != guiprof._products.end(); ++it) - { - ProfProduct* prd = *it; - os << "vendor).toUtf8().constData() << "\" name=\"" << xmlify(prd->productName).toUtf8().constData() << "\""; - if ( ! prd->productRelease.isNull() ) { - os << " release=\"" << xmlify(prd->productRelease).toUtf8().constData() << "\""; - } - if ( ! prd->comment.isNull() ) { - os << " comment=\"" << xmlify(prd->comment).toUtf8().constData() << "\""; - } - os << " />" << endl; - } // for all products - os << endl; - - foreach ( ProfControl* profControl, guiprof.getControls() ) - { - os << "id).toUtf8().constData() << "\"" ; - if ( !profControl->name.isNull() && profControl->name != profControl->id ) { - os << " name=\"" << xmlify(profControl->name).toUtf8().constData() << "\"" ; - } - os << " subcontrols=\"" << xmlify( profControl->renderSubcontrols().toUtf8().constData()) << "\"" ; - os << " show=\"" << xmlify(profControl->getVisibility().getId().toUtf8().constData()) << "\"" ; - if ( profControl->isMandatory() ) { - os << " mandatory=\"true\""; - } - if ( profControl->isSplit() ) { - os << " split=\"true\""; - } - os << " />" << endl; - } // for all controls - os << endl; - - os << "" << endl; -// qCDebug(KMIX_LOG) << "EXIT QTextStream& operator<<"; - return os; -} - -std::ostream& operator<<(std::ostream& os, const GUIProfile& guiprof) { - os << "Soundcard:" << std::endl - << " Driver=" << guiprof._soundcardDriver.toUtf8().constData() << std::endl - << " Driver-Version min=" << guiprof._driverVersionMin - << " max=" << guiprof._driverVersionMax << std::endl - << " Card-Name=" << guiprof._soundcardName.toUtf8().constData() << std::endl - << " Card-Type=" << guiprof._soundcardType.toUtf8().constData() << std::endl - << " Profile-Generation=" << guiprof._generation - << std::endl; - - os << "Profile:" << std::endl - << " Id=" << guiprof._id.toUtf8().constData() << std::endl - << " Name=" << guiprof._name.toUtf8().constData() << std::endl; - - for ( GUIProfile::ProductSet::const_iterator it = guiprof._products.begin(); it != guiprof._products.end(); ++it) - { - ProfProduct* prd = *it; - os << "Product:\n Vendor=" << prd->vendor.toUtf8().constData() << std::endl << " Name=" << prd->productName.toUtf8().constData() << std::endl; - if ( ! prd->productRelease.isNull() ) { - os << " Release=" << prd->productRelease.toUtf8().constData() << std::endl; - } - if ( ! prd->comment.isNull() ) { - os << " Comment = " << prd->comment.toUtf8().constData() << std::endl; - } - } // for all products - - foreach ( ProfControl* profControl, guiprof.getControls() ) - { -// ProfControl* profControl = *it; - os << "Control:\n ID=" << profControl->id.toUtf8().constData() << std::endl; - if ( !profControl->name.isNull() && profControl->name != profControl->id ) { - os << " Name = " << profControl->name.toUtf8().constData() << std::endl; - } - os << " Subcontrols=" << profControl->renderSubcontrols().toUtf8().constData() << std::endl; - if ( profControl->isMandatory() ) { - os << " mandatory=\"true\"" << std::endl; - } - if ( profControl->isSplit() ) { - os << " split=\"true\"" << std::endl; - } - } // for all controls - - return os; -} - -ProfControl::ProfControl(QString& id, QString& subcontrols ) : - visibility(GuiVisibility::GuiSIMPLE), _mandatory(false), _split(false) -{ - d = new ProfControlPrivate(); - this->id = id; setSubcontrols(subcontrols); } -ProfControl::ProfControl(const ProfControl &profControl) : - visibility(profControl.visibility), _mandatory(false), _split(false) +ProfControl::ProfControl(const ProfControl &profControl) + : _mandatory(false), + _split(false) { - d = new ProfControlPrivate(); - id = profControl.id; - name = profControl.name; + _id = profControl._id; + _name = profControl._name; + _visibility = profControl._visibility; _useSubcontrolPlayback = profControl._useSubcontrolPlayback; _useSubcontrolCapture = profControl._useSubcontrolCapture; _useSubcontrolPlaybackSwitch = profControl._useSubcontrolPlaybackSwitch; _useSubcontrolCaptureSwitch = profControl._useSubcontrolCaptureSwitch; _useSubcontrolEnum = profControl._useSubcontrolEnum; - d->subcontrols = profControl.d->subcontrols; + _subcontrols = profControl._subcontrols; - name = profControl.name; - backgroundColor = profControl.backgroundColor; - switchtype = profControl.switchtype; + _backgroundColor = profControl._backgroundColor; + _switchtype = profControl._switchtype; _mandatory = profControl._mandatory; _split = profControl._split; } -ProfControl::~ProfControl() { - delete d; + +bool ProfControl::satisfiesVisibility(GuiVisibility vis) const +{ + GuiVisibility me = getVisibility(); + if (me==GuiVisibility::Never || vis==GuiVisibility::Never) return (false); + if (me==GuiVisibility::Custom || vis==GuiVisibility::Custom) return (false); + if (vis==GuiVisibility::Default) return (true); + return (static_cast(me)<=static_cast(vis)); } + /** - * An overridden method for #setVisible(const GuiVisibility&), that either sets GuiVisibility::GuiSIMPLE - * or GuiVisibility::GuiNEVER; - * - * @param visible + * An overridden method that either sets + * GuiVisibility::Simple or GuiVisibility::Never. */ void ProfControl::setVisible(bool visible) { - this->visibility = visible ? GuiVisibility::GuiSIMPLE : GuiVisibility::GuiNEVER; + setVisibility(visible ? GuiVisibility::Simple : GuiVisibility::Never); +} + +void ProfControl::setVisibility(GuiVisibility vis) +{ + _visibility = vis; } -void ProfControl::setVisible(const GuiVisibility& visibility) +void ProfControl::setVisibility(const QString &visString) { - this->visibility = visibility; + setVisibility(visibilityFromString(visString)); } void ProfControl::setSubcontrols(QString sctls) { - d->subcontrols = sctls; + _subcontrols = sctls; _useSubcontrolPlayback = false; _useSubcontrolCapture = false; _useSubcontrolPlaybackSwitch = false; _useSubcontrolCaptureSwitch = false; _useSubcontrolEnum = false; QStringList qsl = sctls.split( ',', QString::SkipEmptyParts, Qt::CaseInsensitive); QStringListIterator qslIt(qsl); while (qslIt.hasNext()) { QString sctl = qslIt.next(); //qCDebug(KMIX_LOG) << "setSubcontrols found: " << sctl.toLocal8Bit().constData(); if ( sctl == "pvolume" ) _useSubcontrolPlayback = true; else if ( sctl == "cvolume" ) _useSubcontrolCapture = true; else if ( sctl == "pswitch" ) _useSubcontrolPlaybackSwitch = true; else if ( sctl == "cswitch" ) _useSubcontrolCaptureSwitch = true; else if ( sctl == "enum" ) _useSubcontrolEnum = true; else if ( sctl == "*" || sctl == ".*") { _useSubcontrolCapture = true; _useSubcontrolCaptureSwitch = true; _useSubcontrolPlayback = true; _useSubcontrolPlaybackSwitch = true; _useSubcontrolEnum = true; } else qCWarning(KMIX_LOG) << "Ignoring unknown subcontrol type '" << sctl << "' in profile"; } } -QString ProfControl::renderSubcontrols() +QString ProfControl::renderSubcontrols() const { QString sctlString; if ( _useSubcontrolPlayback && _useSubcontrolPlaybackSwitch && _useSubcontrolCapture && _useSubcontrolCaptureSwitch && _useSubcontrolEnum ) { return QString("*"); } else { if ( _useSubcontrolPlayback ) { sctlString += "pvolume,"; } if ( _useSubcontrolCapture ) { sctlString += "cvolume,"; } if ( _useSubcontrolPlaybackSwitch ) { sctlString += "pswitch,"; } if ( _useSubcontrolCaptureSwitch ) { sctlString += "cswitch,"; } if ( _useSubcontrolEnum ) { sctlString += "enum,"; } if ( sctlString.length() > 0 ) { sctlString.chop(1); } return sctlString; } } // ### PARSER START ################################################ GUIProfileParser::GUIProfileParser(GUIProfile* ref_gp) : _guiProfile(ref_gp) { _scope = GUIProfileParser::NONE; // no scope yet } bool GUIProfileParser::startDocument() { _scope = GUIProfileParser::NONE; // no scope yet return true; } bool GUIProfileParser::startElement( const QString& , const QString& , const QString& qName, const QXmlAttributes& attributes ) { switch ( _scope ) { case GUIProfileParser::NONE: /** we are reading the "top level" ***************************/ if ( qName.toLower() == "soundcard" ) { _scope = GUIProfileParser::SOUNDCARD; addSoundcard(attributes); } else { // skip unknown top-level nodes - std::cerr << "Ignoring unsupported element '" << qName.toUtf8().constData() << "'" << std::endl; + qCWarning(KMIX_LOG) << "Ignoring unsupported element" << qName; } // we are accepting only break; case GUIProfileParser::SOUNDCARD: if ( qName.toLower() == "product" ) { // Defines product names under which the chipset/hardware is sold addProduct(attributes); } else if ( qName.toLower() == "control" ) { addControl(attributes); } else if ( qName.toLower() == "profile" ) { addProfileInfo(attributes); } else { - std::cerr << "Ignoring unsupported element '" << qName.toUtf8().constData() << "'" << std::endl; + qCWarning(KMIX_LOG) << "Ignoring unsupported element" << qName; } // we are accepting , and break; } // switch() return true; } bool GUIProfileParser::endElement( const QString&, const QString&, const QString& qName ) { if ( qName == "soundcard" ) { _scope = GUIProfileParser::NONE; // should work out OK, as we don't nest soundcard entries } return true; } void GUIProfileParser::addSoundcard(const QXmlAttributes& attributes) { /* std::cout << "Soundcard: "; printAttributes(attributes); */ QString driver = attributes.value("driver"); QString version = attributes.value("version"); QString name = attributes.value("name"); QString type = attributes.value("type"); QString generation = attributes.value("generation"); if ( !driver.isNull() && !name.isNull() ) { _guiProfile->_soundcardDriver = driver; _guiProfile->_soundcardName = name; if ( type.isNull() ) { _guiProfile->_soundcardType = ""; } else { _guiProfile->_soundcardType = type; } if ( version.isNull() ) { _guiProfile->_driverVersionMin = 0; _guiProfile->_driverVersionMax = 0; } else { std::pair versionMinMax; splitPair(version, versionMinMax, ':'); _guiProfile->_driverVersionMin = versionMinMax.first.toULong(); _guiProfile->_driverVersionMax = versionMinMax.second.toULong(); } if ( type.isNull() ) { type = ""; }; if ( generation.isNull() ) { _guiProfile->_generation = 0; } else { // Hint: If the conversion fails, _generation will be assigned 0 (which is fine) _guiProfile->_generation = generation.toUInt(); } } } void GUIProfileParser::addProfileInfo(const QXmlAttributes& attributes) { QString name = attributes.value("name"); QString id = attributes.value("id"); _guiProfile->setId(id); _guiProfile->setName(name); } void GUIProfileParser::addProduct(const QXmlAttributes& attributes) { /* std::cout << "Product: "; printAttributes(attributes); */ QString vendor = attributes.value("vendor"); QString name = attributes.value("name"); QString release = attributes.value("release"); QString comment = attributes.value("comment"); if ( !vendor.isNull() && !name.isNull() ) { // Adding a product makes only sense if we have at least vendor and product name ProfProduct *prd = new ProfProduct(); prd->vendor = vendor; prd->productName = name; prd->productRelease = release; prd->comment = comment; _guiProfile->addProduct(prd); } } void GUIProfileParser::addControl(const QXmlAttributes& attributes) { /* std::cout << "Control: "; printAttributes(attributes); */ QString id = attributes.value("id"); QString subcontrols = attributes.value("subcontrols"); QString name = attributes.value("name"); QString show = attributes.value("show"); QString background = attributes.value("background"); QString switchtype = attributes.value("switchtype"); QString mandatory = attributes.value("mandatory"); QString split = attributes.value("split"); bool isMandatory = false; if ( !id.isNull() ) { // We need at least an "id". We can set defaults for the rest, if undefined. if ( subcontrols.isNull() || subcontrols.isEmpty() ) { subcontrols = '*'; // for compatibility reasons, we interpret an empty string as match-all (aka "*") } if ( name.isNull() ) { // ignore. isNull() will be checked by all users. } if ( ! mandatory.isNull() && mandatory == "true" ) { isMandatory = true; } if ( !background.isNull() ) { // ignore. isNull() will be checked by all users. } if ( !switchtype.isNull() ) { // ignore. isNull() will be checked by all users. } ProfControl *profControl = new ProfControl(id, subcontrols); - if ( show.isNull() ) { show = '*'; } - profControl->name = name; - profControl->setVisible(GuiVisibility::getByString(show)); + profControl->setName(name); + profControl->setVisibility(show.isNull() ? "all" : show); profControl->setBackgroundColor( background ); profControl->setSwitchtype(switchtype); profControl->setMandatory(isMandatory); - - if ( !split.isNull() && split=="true") { - profControl->setSplit(true); - } - _guiProfile->getControls().push_back(profControl); + if (split=="true") profControl->setSplit(true); + + _guiProfile->addControl(profControl); } // id != null } -void GUIProfileParser::printAttributes(const QXmlAttributes& attributes) { - if ( attributes.length() > 0 ) { - for ( int i = 0 ; i < attributes.length(); i++ ) { - std::cout << attributes.qName(i).toUtf8().constData() << ":"<< attributes.value(i).toUtf8().constData() << " , "; - } - std::cout << std::endl; - } +void GUIProfileParser::printAttributes(const QXmlAttributes& attributes) +{ + if (attributes.length() > 0 ) { + for ( int i = 0 ; i < attributes.length(); i++ ) { + qCDebug(KMIX_LOG) << i << attributes.qName(i) << "="<< attributes.value(i); + } + } } void GUIProfileParser::splitPair(const QString& pairString, std::pair& result, char delim) { int delimPos = pairString.indexOf(delim); if ( delimPos == -1 ) { // delimiter not found => use an empty String for "second" result.first = pairString; result.second = ""; } else { // delimiter found result.first = pairString.mid(0,delimPos); result.second = pairString.left(delimPos+1); } } diff --git a/gui/guiprofile.h b/gui/guiprofile.h index fd5336bc..206c041d 100644 --- a/gui/guiprofile.h +++ b/gui/guiprofile.h @@ -1,327 +1,230 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 2006-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. */ #ifndef _GUIPROFILE_H_ #define _GUIPROFILE_H_ class Mixer; #include "kmix_debug.h" #include #include #include #include #include -#include #include -#include #include -struct SortedStringComparator -{ - bool operator()(const std::string&, const std::string&) const; -}; - struct ProfProduct { QString vendor; QString productName; // In case the vendor ships different products under the same productName QString productRelease; QString comment; }; -class ProfControlPrivate -{ -public: - // List of controls, e.g: "rec:1-2,recswitch" - // THIS IS RAW DATA AS LOADED FROM THE PROFILE. DO NOT USE IT, except for debugging. - QString subcontrols; - -}; /** * GuiVisibility can be used in different contexts. One is, to define in the XML GUI Profile, which control to show, e.g. show * "MIC Boost" in EXTENDED mode. The other is for representing the GUI complexity (e.g. for letting the user select a preset like "SIMPLE". */ -class GuiVisibility +enum class GuiVisibility { - enum GuiVisibilityId { SIMPLE, EXTENDED, FULL, CUSTOM, NEVER }; - QString id; - GuiVisibilityId idCode; - -public: -static GuiVisibility const GuiSIMPLE; -static GuiVisibility const GuiEXTENDED; -static GuiVisibility const GuiFULL; -static GuiVisibility const GuiCUSTOM; -static GuiVisibility const GuiNEVER; // e.g. templates with regexp's - - private: - GuiVisibility(QString id, GuiVisibilityId idCode) - { - this->id = id; - this->idCode = idCode; - } - - public: - QString& getId() - { - return id; - } - - /** - * Returns whether this GuiVisibility satisfies the other GuiVisibility. - * GuiNEVER can never be satisfied - if this or other is GuiNEVER, the result is false. - * GuiCUSTOM is always satisfied - if this or other is GuiCUSTOM, the result is true. - * The other 3 enum values are completely ordered as GuiSIMPLE, GuiEXTENDED, GuiFULL. - *

- * For example - * GuiSIMPLE satisfies GuiFULL, as simple GUI is part of full GUI. - * - * @param other - * @return - */ - bool satisfiesVisibility(GuiVisibility& other) const - { - if (this->idCode == GuiVisibility::NEVER || other.idCode == GuiVisibility::NEVER) - return false; - if (this->idCode == GuiVisibility::CUSTOM || other.idCode == GuiVisibility::CUSTOM) - return false; - - return this->idCode <= other.idCode; - } - - /** - * Returns the static GuiVisibility represented by the given string. - * For illegal string values, GuiFULL will be returned. - * - * @param string - * @return - */ - static const GuiVisibility& getByString(QString& string) - { - if (string == GuiSIMPLE.id) - return GuiSIMPLE; - if (string == GuiEXTENDED.id) - return GuiEXTENDED; - if (string == GuiFULL.id) - return GuiFULL; - if (string == GuiCUSTOM.id) - return GuiCUSTOM; - if (string == GuiNEVER.id) - return GuiNEVER; - - qCWarning(KMIX_LOG) << "Unknown GuiVisibility=" << string << ". Applying default=" << GuiFULL.id; - return GuiFULL; - } - - bool operator==(const GuiVisibility &other) const - { - return idCode == other.idCode; - } - + Simple, + Extended, + Full, + Custom, + Never, + Default }; - class ProfControl { public: - ProfControl(QString& id, QString& subcontrols); + ProfControl(const QString &id, const QString &subcontrols); ProfControl(const ProfControl &ctl); // copy constructor - ~ProfControl(); - // ID as returned by the Mixer Backend, e.g. Master:0 - QString id; + ~ProfControl() = default; - void setSubcontrols(QString sctls); - bool useSubcontrolPlayback() {return _useSubcontrolPlayback;}; - bool useSubcontrolCapture() {return _useSubcontrolCapture;}; - bool useSubcontrolPlaybackSwitch() {return _useSubcontrolPlaybackSwitch;}; - bool useSubcontrolCaptureSwitch() {return _useSubcontrolCaptureSwitch;}; - bool useSubcontrolEnum() {return _useSubcontrolEnum;}; - QString renderSubcontrols(); - - QString getBackgroundColor() const { return backgroundColor; } - void setBackgroundColor(QString& backgroundColor) { this->backgroundColor = backgroundColor; } - QString getSwitchtype() const { return switchtype; } - void setSwitchtype(QString switchtype) { this->switchtype = switchtype; } - - // Visible name for the User ( if name.isNull(), id will be used - And in the future a default lookup table will be consulted ). + // ID as returned by the Mixer Backend, e.g. "Master:0" + QString id() const { return (_id); } + void setId(const QString &id) { _id = id; } + + // Visible name for the User (if null, 'id' will be used). + // And in the future a default lookup table will be consulted. // Because the name is visible, some kind of i18n() should be used. - QString name; - - void setVisible(bool); - void setVisible(const GuiVisibility& visibility); - GuiVisibility& getVisibility() { return visibility; }; - - bool isMandatory() const - { - return _mandatory; - } - - void setMandatory(bool _mandatory) - { - this->_mandatory = _mandatory; - } - void setSplit ( bool split ) { - _split = split; - } - bool isSplit() const { - return _split; - } + QString name() const { return (_name); } + void setName(const QString &name) { _name = name; } + + void setSubcontrols(QString sctls); + bool useSubcontrolPlayback() const { return (_useSubcontrolPlayback); } + bool useSubcontrolCapture() const { return (_useSubcontrolCapture); } + bool useSubcontrolPlaybackSwitch() const { return (_useSubcontrolPlaybackSwitch); } + bool useSubcontrolCaptureSwitch() const { return (_useSubcontrolCaptureSwitch); } + bool useSubcontrolEnum() const { return (_useSubcontrolEnum); } + QString renderSubcontrols() const; + + QString getBackgroundColor() const { return (_backgroundColor); } + void setBackgroundColor(const QString &col) { _backgroundColor = col; } + QString getSwitchtype() const { return (_switchtype); } + void setSwitchtype(const QString &swtype) { _switchtype = swtype; } + + void setVisible(bool visible); + void setVisibility(GuiVisibility vis); + void setVisibility(const QString &visString); + GuiVisibility getVisibility() const { return (_visibility); } + + bool isMandatory() const { return (_mandatory); } + void setMandatory(bool mandatory) { _mandatory = mandatory; } + + void setSplit (bool split) { _split = split; } + bool isSplit() const { return (_split); } + + /** + * Returns whether this ProfControl's GuiVisibility satisfies the other GuiVisibility. + * 'Never' can never be satisfied - if this or the other is 'Never', the result is false. + * 'Custom' is always satisfied - if this or the other is 'Custom', the result is true. + * 'Default' for the other is always satisfied. + * The other 3 enum values are completely ordered as 'Simple' < 'Extended' < 'Full'. + *

+ * For example, 'Simple' satisfies 'Full', as the simple GUI is part of the full GUI. + */ + bool satisfiesVisibility(GuiVisibility vis) const; private: // The following are the deserialized values of _subcontrols bool _useSubcontrolPlayback; bool _useSubcontrolCapture; bool _useSubcontrolPlaybackSwitch; bool _useSubcontrolCaptureSwitch; bool _useSubcontrolEnum; + QString _id; + QString _name; + // For applying custom colors - QString backgroundColor; + QString _backgroundColor; // For defining the switch type when it is not a standard palyback or capture switch - QString switchtype; + QString _switchtype; // show or hide (contains the GUI type: simple, extended, all) - - GuiVisibility visibility; + GuiVisibility _visibility; bool _mandatory; // A mandatory control must be included in all GUIProfile copies - - ProfControlPrivate *d; bool _split; // true if this widget is to show two sliders + + // List of controls, e.g: "rec:1-2,recswitch" + // THIS IS RAW DATA AS LOADED FROM THE PROFILE. DO NOT USE IT, except for debugging. + QString _subcontrols; }; struct ProductComparator { bool operator()(const ProfProduct*, const ProfProduct*) const; }; + class GUIProfile { public: typedef std::set ProductSet; typedef QList ControlSet; -private: - static QMap& getProfiles() { return s_profiles; } - // Loading - static QString buildProfileName(Mixer* mixer, QString profileName, bool ignoreCard); - static QString buildReadableProfileName(Mixer* mixer, QString profileName); - - static GUIProfile* loadProfileFromXMLfiles(Mixer* mixer, QString profileName); - static void addProfile(GUIProfile* guiprof); - static const QString createNormalizedFilename(const QString& profileId); - - static QMap s_profiles; - - public: GUIProfile(); - virtual ~GUIProfile(); + ~GUIProfile(); - static void clearCache(); - - bool readProfile(const QString& ref_fileNamestring); + bool readProfile(const QString &ref_fileNamestring); bool finalizeProfile() const; bool writeProfile(); - bool isDirty() const; - void setDirty(); - - void setId(const QString& id); - QString getId() const; - QString getMixerId() const { return _mixerId; } + bool isDirty() const { return (_dirty); } + void setDirty() { _dirty = true; } + void setId(const QString &id) { _id = id; } + QString getId() const { return (_id); } + QString getMixerId() const { return (_mixerId); } - unsigned long match(Mixer* mixer); - friend std::ostream& operator<<(std::ostream& os, const GUIProfile& vol); - friend QTextStream& operator<<(QTextStream &outStream, const GUIProfile& guiprof); + unsigned long match(const Mixer *mixer) const; - - - static GUIProfile* find(Mixer* mixer, QString profileName, bool profileNameIsFullyQualified, bool ignoreCardName); - static GUIProfile* find(QString id); - static GUIProfile* selectProfileFromXMLfiles(Mixer*, QString preferredProfile); - static GUIProfile* fallbackProfile(Mixer*); + static void clearCache(); + static GUIProfile *find(const QString &id); + static GUIProfile *find(const Mixer *mixer, const QString &profileName, bool profileNameIsFullyQualified, bool ignoreCardName); + static GUIProfile *fallbackProfile(const Mixer *mixer); // --- Getters and setters ---------------------------------------------------------------------- - const ControlSet& getControls() const; - ControlSet& getControls(); - void setControls(ControlSet& newControlSet); - - QString getName() const { return _name; } - void setName(QString _name) { this->_name = _name; } + const ControlSet &getControls() const { return (_controls); } - void addProduct(ProfProduct*); + void setControls(ControlSet &newControlSet); + void addControl(ProfControl *ctrl) { _controls.push_back(ctrl); } + void addProduct(ProfProduct *prod) { _products.insert(prod); } + QString getName() const { return (_name); } + void setName(const QString &name) { _name = name; } // --- The values from the tag: No getters and setters for them (yet) ----------------------------- QString _soundcardDriver; // The driver version: 1000*1000*MAJOR + 1000*MINOR + PATCHLEVEL unsigned long _driverVersionMin; unsigned long _driverVersionMax; QString _soundcardName; QString _soundcardType; unsigned long _generation; private: ControlSet _controls; ProductSet _products; QString _id; QString _name; QString _mixerId; bool _dirty; }; -std::ostream& operator<<(std::ostream& os, const GUIProfile& vol); -QTextStream& operator<<(QTextStream &outStream, const GUIProfile& guiprof); class GUIProfileParser : public QXmlDefaultHandler { public: explicit GUIProfileParser(GUIProfile* ref_gp); // Enumeration for the scope enum ProfileScope { NONE, SOUNDCARD }; bool startDocument() Q_DECL_OVERRIDE; bool startElement( const QString&, const QString&, const QString& , const QXmlAttributes& ) Q_DECL_OVERRIDE; bool endElement( const QString&, const QString&, const QString& ) Q_DECL_OVERRIDE; private: void addControl(const QXmlAttributes& attributes); void addProduct(const QXmlAttributes& attributes); void addSoundcard(const QXmlAttributes& attributes); void addProfileInfo(const QXmlAttributes& attributes); void printAttributes(const QXmlAttributes& attributes); void splitPair(const QString& pairString, std::pair& result, char delim); ProfileScope _scope; GUIProfile* _guiProfile; }; #endif //_GUIPROFILE_H_ diff --git a/gui/mdwenum.cpp b/gui/mdwenum.cpp index 59be404c..467d4aaf 100644 --- a/gui/mdwenum.cpp +++ b/gui/mdwenum.cpp @@ -1,196 +1,190 @@ /* * 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. */ // KMix #include "mdwenum.h" -#include "core/mixer.h" #include "viewbase.h" +#include "core/mixer.h" // KDE #include -#include -#include #include #include // Qt #include #include #include #include #include #include #include /** * Class that represents an Enum element (a select one-from-many selector) * The orientation (horizontal, vertical) is ignored */ -MDWEnum::MDWEnum( shared_ptr md, - Qt::Orientation orientation, - QWidget* parent, ViewBase* view, ProfControl* par_pctl) : - MixDeviceWidget(md, false, orientation, parent, view, par_pctl), - _label(0), _enumCombo(0), _layout(0) +MDWEnum::MDWEnum(shared_ptr md, MixDeviceWidget::MDWFlags flags, ViewBase *view, ProfControl *pctl) + : MixDeviceWidget(md, flags, view, pctl), + _label(nullptr), + _enumCombo(nullptr) { // create actions (on _mdwActions, see MixDeviceWidget) // KStandardAction::showMenubar() is in MixDeviceWidget now KToggleAction *action = _mdwActions->add( "hide" ); action->setText( i18n("&Hide") ); connect(action, SIGNAL(triggered(bool)), SLOT(setDisabled(bool))); QAction *c = _mdwActions->addAction( "keys" ); c->setText( i18n("C&onfigure Shortcuts...") ); connect(c, SIGNAL(triggered(bool)), SLOT(defineKeys())); // create widgets createWidgets(); - - /* remove this for production version - QAction *a = _mdwActions->addAction( "Next Value" ); - c->setText( i18n( "Next Value" ) ); - connect(a, SIGNAL(triggered(bool)), SLOT(nextEnumId())); - */ - - installEventFilter( this ); // filter for popup -} - -MDWEnum::~MDWEnum() -{ } void MDWEnum::createWidgets() { - if ( _orientation == Qt::Vertical ) { - _layout = new QVBoxLayout( this ); - _layout->setAlignment(Qt::AlignLeft); - } - else { - _layout = new QHBoxLayout( this ); - _layout->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); - } - - _label = new QLabel( m_mixdevice->readableName(), this); + QBoxLayout *_layout; + if (orientation()==Qt::Vertical) + { + _layout = new QVBoxLayout(this); + _layout->setAlignment(Qt::AlignLeft|Qt::AlignTop); + } + else + { + _layout = new QHBoxLayout(this); + _layout->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); + } + + _label = new QLabel( mixDevice()->readableName(), this); _layout->addWidget(_label); + + if (orientation()==Qt::Horizontal) _layout->addSpacing(8); + _enumCombo = new QComboBox(this); - _enumCombo->installEventFilter(this); + _enumCombo->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + // ------------ fill ComboBox start ------------ - int maxEnumId= m_mixdevice->enumValues().count(); + const QStringList &values = mixDevice()->enumValues(); + int maxEnumId = values.count(); for (int i=0; iaddItem( m_mixdevice->enumValues().at(i)); + _enumCombo->addItem(values.at(i)); } // ------------ fill ComboBox end -------------- _layout->addWidget(_enumCombo); connect( _enumCombo, SIGNAL(activated(int)), this, SLOT(setEnumId(int)) ); - _enumCombo->setToolTip( m_mixdevice->readableName() ); + _enumCombo->setToolTip( mixDevice()->readableName() ); _layout->addStretch(1); } void MDWEnum::update() { - if ( m_mixdevice->isEnum() ) { - //qCDebug(KMIX_LOG) << "MDWEnum::update() enumID=" << m_mixdevice->enumId(); - _enumCombo->setCurrentIndex( m_mixdevice->enumId() ); + if ( mixDevice()->isEnum() ) { + //qCDebug(KMIX_LOG) << "MDWEnum::update() enumID=" << mixDevice()->enumId(); + _enumCombo->setCurrentIndex( mixDevice()->enumId() ); } else { - qCCritical(KMIX_LOG) << "MDWEnum::update() enumID=" << m_mixdevice->enumId() << " is no Enum ... skipped"; + qCCritical(KMIX_LOG) << "MDWEnum::update() enumID=" << mixDevice()->enumId() << " is no Enum ... skipped"; } } void MDWEnum::showContextMenu(const QPoint& pos ) { - if( m_view == 0 ) - return; - - QMenu *menu = m_view->getPopup(); + if (view()==nullptr) return; - menu->popup( pos ); + QMenu *menu = view()->getPopup(); + menu->popup(pos); } QSizePolicy MDWEnum::sizePolicy() const { return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); } /** This slot is called, when a user has clicked the mute button. Also it is called by any other associated KAction like the context menu. */ void MDWEnum::nextEnumId() { - if( m_mixdevice->isEnum() ) { + if( mixDevice()->isEnum() ) { int curEnum = enumId(); - if ( curEnum < m_mixdevice->enumValues().count() ) { + if ( curEnum < mixDevice()->enumValues().count() ) { // next enum value setEnumId(curEnum+1); } else { // wrap around setEnumId(0); } } // isEnum } void MDWEnum::setEnumId(int value) { - if ( m_mixdevice->isEnum() ) { - m_mixdevice->setEnumId( value ); - m_mixdevice->mixer()->commitVolumeChange( m_mixdevice ); + if ( mixDevice()->isEnum() ) { + mixDevice()->setEnumId( value ); + mixDevice()->mixer()->commitVolumeChange( mixDevice() ); } } int MDWEnum::enumId() { - if ( m_mixdevice->isEnum() ) { - return m_mixdevice->enumId(); + if ( mixDevice()->isEnum() ) { + return mixDevice()->enumId(); } else { return 0; } } void MDWEnum::setDisabled( bool hide ) { emit guiVisibilityChange(this, !hide); } /** - * An event filter for the various QWidgets. We watch for Mouse press Events, so - * that we can popup the context menu. + * For users of this class who would like to show multiple MDWEnum's properly aligned. + * It returns the size of the control label (in the control layout direction). */ -bool MDWEnum::eventFilter( QObject* obj, QEvent* e ) +int MDWEnum::labelExtentHint() const { - if (e->type() == QEvent::MouseButtonPress) { - QMouseEvent *qme = static_cast(e); - if (qme->button() == Qt::RightButton) { - showContextMenu(); - return true; - } - } else if (e->type() == QEvent::ContextMenu) { - QPoint pos = reinterpret_cast(obj)->mapToGlobal(QPoint(0, 0)); - showContextMenu(pos); - return true; - } - return QWidget::eventFilter(obj,e); + if (_label==nullptr) return (0); + + if (orientation()==Qt::Vertical) return (_label->sizeHint().height()); + else return (_label->sizeHint().width()); } +/** + * If a label from another switch is larger than ours, then the + * extent of our label is adjusted. + */ +void MDWEnum::setLabelExtent(int extent) +{ + if (_label==nullptr) return; + + if (orientation()==Qt::Vertical) _label->setMinimumHeight(extent); + else _label->setMinimumWidth(extent); +} diff --git a/gui/mdwenum.h b/gui/mdwenum.h index 6d523abc..6ac70664 100644 --- a/gui/mdwenum.h +++ b/gui/mdwenum.h @@ -1,74 +1,68 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2004 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 MDWENUM_H #define MDWENUM_H -#include -#include "core/volume.h" - // KMix class MixDevice; class ViewBase; // Qt -class QBoxLayout; class QComboBox; class QLabel; #include "gui/mixdevicewidget.h" class MDWEnum : public MixDeviceWidget { Q_OBJECT public: - MDWEnum( shared_ptr md, - Qt::Orientation orientation, - QWidget* parent, ViewBase* view, ProfControl* pctl); - ~MDWEnum(); + MDWEnum(shared_ptr md, MixDeviceWidget::MDWFlags flags, ViewBase *view, ProfControl *pctl = nullptr); + virtual ~MDWEnum() = default; void addActionToPopup( QAction *action ); QSizePolicy sizePolicy() const; - bool eventFilter( QObject* obj, QEvent* e ) Q_DECL_OVERRIDE; + int labelExtentHint() const Q_DECL_OVERRIDE; + void setLabelExtent(int extent) Q_DECL_OVERRIDE; public slots: // GUI hide and show void setDisabled(bool) Q_DECL_OVERRIDE; // Enum handling: next and selecting void nextEnumId(); int enumId(); void setEnumId(int value); void update() Q_DECL_OVERRIDE; void showContextMenu(const QPoint& pos = QCursor::pos()) Q_DECL_OVERRIDE; private: void createWidgets(); QLabel *_label; QComboBox *_enumCombo; - QBoxLayout *_layout; }; #endif diff --git a/gui/mdwslider.cpp b/gui/mdwslider.cpp index 44a89864..c234fdfd 100644 --- a/gui/mdwslider.cpp +++ b/gui/mdwslider.cpp @@ -1,1307 +1,1241 @@ /* * 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 #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/mdwmoveaction.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, bool showMuteLED, bool showCaptureLED - , bool includeMixerName, bool small, Qt::Orientation orientation, QWidget* parent - , ViewBase* view - , ProfControl* par_ctl - ) : - MixDeviceWidget(md,small,orientation,parent,view, par_ctl), - m_linked(true), muteButtonSpacer(0), captureSpacer(0), labelSpacer(0), - m_iconLabelSimple(0), m_qcb(0), m_muteText(0), - m_label( 0 ), - mediaButton(0), - m_captureCheckbox(0), m_captureText(0), labelSpacing(0), - muteButtonSpacing(false), captureLEDSpacing(false), _mdwMoveActions(new KActionCollection(this)), m_moveMenu(0), - m_sliderInWork(0), m_waitForSoundSetComplete(0) +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()), + _mdwMoveActions(new KActionCollection(this)), + m_moveMenu(nullptr), + m_sliderInWork(false), + m_waitForSoundSetComplete(0) { + //qCDebug(KMIX_LOG) << "for" << mixDevice()->readableName() << "flags" << MixDeviceWidget::flags(); + createActions(); - createWidgets( showMuteLED, showCaptureLED, includeMixerName ); + createWidgets(); createShortcutActions(); - installEventFilter( this ); // filter for popup + + // 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() { - foreach( QAbstractSlider* slider, m_slidersPlayback) - { - delete slider; - } - foreach( QAbstractSlider* slider, m_slidersCapture) - { - delete slider; - } + 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 ( ! m_mixdevice->mixer()->isDynamic() ) { +// if ( ! mixDevice()->mixer()->isDynamic() ) { // action = _mdwActions->add( "hide" ); // action->setText( i18n("&Hide") ); // connect( action, SIGNAL(triggered(bool)), SLOT(setDisabled(bool)) ); // } - if( m_mixdevice->hasMuteSwitch() ) + if( mixDevice()->hasMuteSwitch() ) { taction = _mdwActions->add( "mute" ); taction->setText( i18n("&Muted") ); connect( taction, SIGNAL(toggled(bool)), SLOT(toggleMuted()) ); } - if( m_mixdevice->captureVolume().hasSwitch() ) { + if( mixDevice()->captureVolume().hasSwitch() ) { taction = _mdwActions->add( "recsrc" ); - taction->setText( i18n("Set &Record Source") ); + taction->setText( i18n("Captu&re") ); connect( taction, SIGNAL(toggled(bool)), SLOT(toggleRecsrc()) ); } - if( m_mixdevice->isMovable() ) { + if( mixDevice()->isMovable() ) { m_moveMenu = new QMenu( i18n("Mo&ve"), this); connect( m_moveMenu, SIGNAL(aboutToShow()), SLOT(showMoveMenu()) ); } QAction* qaction = _mdwActions->addAction( "keys" ); - qaction->setText( i18n("C&onfigure Shortcuts...") ); + 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 ) + if (orientation()==Qt::Vertical) { return QSizePolicy( QSizePolicy::Preferred, QSizePolicy::MinimumExpanding ); } else { return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); -// return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); } } 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 widgets label. + * 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 ( _orientation == Qt::Vertical && m_label ) { - return m_label->heightForWidth(m_label->minimumWidth()); - } - return 0; + 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 ( _orientation == Qt::Vertical ) - { - int extentHint = labelExtentHint(); - int spacerHeight = (extent > extentHint) ? extent - extentHint : 0; - labelSpacer->setFixedHeight(spacerHeight); - } +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_qcb!=0; + return (m_muteButton!=nullptr); } -/** - * If this widget does not have a mute button, but another widget has, we add a spacer here with the - * size of a QToolButton (don't know how to make a better estimate) - */ -void MDWSlider::setMuteButtonSpace(bool value) -{ - if (hasMuteButton() || !value) { - muteButtonSpacer->setFixedSize(0,0); - muteButtonSpacer->setVisible(false); - } else { - QToolButton b; - muteButtonSpacer->setFixedSize( b.sizeHint() ); - } -} - /** * See "hasMuteButton" */ bool MDWSlider::hasCaptureLED() const { - return m_captureCheckbox!=0; + return (m_captureButton!=nullptr); } -/** - * See "setMuteButtonSpace" - */ -void MDWSlider::setCaptureLEDSpace(bool showCaptureLED) +void MDWSlider::guiAddCaptureButton(const QString &captureTooltipText) { - if ( !showCaptureLED || hasCaptureLED() ) { - captureSpacer->setFixedSize(0,0); - captureSpacer->setVisible(false); - } else - captureSpacer->setFixedSize(QCheckBox().sizeHint()); + 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::guiAddSlidersAndMediacontrols(bool playSliders, bool capSliders, bool mediaControls, QBoxLayout* layout, const QString& tooltipText, const QString& captureTooltipText) +void MDWSlider::guiAddMuteButton(const QString &muteTooltipText) { - if (playSliders) - addSliders(layout, 'p', m_mixdevice->playbackVolume(), m_slidersPlayback, tooltipText); + 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); +} - if (capSliders) - addSliders(layout, 'c', m_mixdevice->captureVolume(), m_slidersCapture, captureTooltipText); +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); +} - if (mediaControls) - addMediaControls(layout); +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); } -void MDWSlider::guiAddCaptureCheckbox(bool wantsCaptureLED, const Qt::Alignment& alignmentForCapture, QBoxLayout* layoutForCapture, const QString& captureTooltipText) +QWidget *MDWSlider::guiAddButtonSpacer() { - if (wantsCaptureLED && m_mixdevice->captureVolume().hasSwitch()) + 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 { - m_captureCheckbox = new QCheckBox(i18n("capture"), this); - m_captureCheckbox->installEventFilter(this); - layoutForCapture->addWidget(m_captureCheckbox, alignmentForCapture); - connect(m_captureCheckbox, SIGNAL(toggled(bool)), this, SLOT(setRecsrc(bool))); - m_captureCheckbox->setToolTip(captureTooltipText); + buttonSpacer->setMinimumWidth(controlButtonSize().width()); + buttonSpacer->setMaximumHeight(1); } + + buttonSpacer->installEventFilter(this); + return (buttonSpacer); } -void MDWSlider::guiAddMuteButton(bool wantsMuteButton, Qt::Alignment alignment, QBoxLayout* layoutForMuteButton, const QString& muteTooltipText) +QSize MDWSlider::controlButtonSize() { - if (wantsMuteButton && m_mixdevice->hasMuteSwitch()) + if (!m_controlButtonSize.isValid()) // not calculated yet { - m_qcb = new QToolButton(this); - m_qcb->setAutoRaise(true); - m_qcb->setCheckable(false); - //m_qcb->setIcon(QIcon(loadIcon("audio-volume-muted"))); - setIcon("audio-volume-muted", m_qcb); - //setIcon("audio-volume-mutd", m_qcb); - layoutForMuteButton->addWidget(m_qcb, 0, alignment); - m_qcb->installEventFilter(this); - connect(m_qcb, SIGNAL(clicked(bool)), this, SLOT(toggleMuted())); - m_qcb->setToolTip(muteTooltipText); + auto *buttonSpacer = new QToolButton(); + ToggleToolButton::setIndicatorIcon("unknown", buttonSpacer, + (flags() & MixDeviceWidget::SmallSize)); + m_controlButtonSize = buttonSpacer->sizeHint(); + qCDebug(KMIX_LOG) << m_controlButtonSize; + delete buttonSpacer; } - // Spacer will be shown, when no mute button is displayed - muteButtonSpacer = new QWidget(this); - layoutForMuteButton->addWidget( muteButtonSpacer ); - muteButtonSpacer->installEventFilter(this); - + return (m_controlButtonSize); } -void MDWSlider::guiAddControlIcon(Qt::Alignment alignment, QBoxLayout* layout, const QString& tooltipText) -{ - m_iconLabelSimple = new QLabel(this); - installEventFilter(m_iconLabelSimple); - setIcon(m_mixdevice->iconName(), m_iconLabelSimple); - m_iconLabelSimple->setToolTip(tooltipText); - layout->addWidget(m_iconLabelSimple, 0, alignment); -} /** * Creates all widgets : Icon, Label, Mute-Button, Slider(s) and Capture-Button. */ -void MDWSlider::createWidgets( bool showMuteButton, bool showCaptureLED, bool includeMixerName ) +void MDWSlider::createWidgets() { - bool includePlayback = _pctl->useSubcontrolPlayback(); - bool includeCapture = _pctl->useSubcontrolCapture(); - bool wantsPlaybackSliders = includePlayback && ( m_mixdevice->playbackVolume().count() > 0 ); - bool wantsCaptureSliders = includeCapture && ( m_mixdevice->captureVolume().count() > 0 ); - bool wantsCaptureLED = showCaptureLED && includeCapture; - bool wantsMuteButton = showMuteButton && includePlayback; - bool hasVolumeSliders = wantsPlaybackSliders || wantsCaptureSliders; - // bool bothCaptureANDPlaybackExist = wantsPlaybackSliders && wantsCaptureSliders; + 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); - MediaController* mediaController = m_mixdevice->getMediaController(); - bool wantsMediaControls = mediaController->hasControls(); - - QString tooltipText = m_mixdevice->readableName(); - QString captureTooltipText( i18n( "Capture/Uncapture %1", m_mixdevice->readableName() ) ); - QString muteTooltipText( i18n( "Mute/Unmute %1", m_mixdevice->readableName() ) ); - if (includeMixerName) { - tooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName(), tooltipText ); - captureTooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName(), captureTooltipText ); - muteTooltipText = QString( "%1\n%2" ).arg( m_mixdevice->mixer()->readableName(), muteTooltipText ); - } - - // case of vertical sliders: - if ( _orientation == Qt::Vertical ) + const MediaController *mediaController = mixDevice()->getMediaController(); + 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) { - QVBoxLayout *controlLayout = new QVBoxLayout(this); - controlLayout->setAlignment(Qt::AlignHCenter|Qt::AlignTop); - setLayout(controlLayout); - controlLayout->setContentsMargins(0,0,0,0); - - guiAddControlIcon(Qt::AlignHCenter|Qt::AlignTop, controlLayout, tooltipText); - - Qt::Alignment centerAlign = Qt::AlignHCenter | Qt::AlignBottom; - - //the device label - m_label = new QLabel( m_mixdevice->readableName(), this); - m_label->setWordWrap(true); - int max = 80; - QStringList words = m_mixdevice->readableName().split(QChar(' ')); - foreach (QString name, words) - max = qMax(max,QLabel(name).sizeHint().width()); -// if (words.size()>1 && m_label) -// m_label->setMinimumWidth(80); -// if (m_label->sizeHint().width()>max && m_label->sizeHint().width()>80) -// m_label->setMinimumWidth(max); - m_label->setMinimumWidth(max); - m_label->setMinimumHeight(m_label->heightForWidth(m_label->minimumWidth())); - m_label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - m_label->setAlignment(Qt::AlignHCenter); - controlLayout->addWidget(m_label, 0, centerAlign ); - - //spacer with height to match height difference to other slider widgets - labelSpacer = new QWidget(this); - controlLayout->addWidget( labelSpacer ); - labelSpacer->installEventFilter(this); - - // sliders - QBoxLayout *volLayout = new QHBoxLayout( ); - volLayout->setAlignment(centerAlign); - controlLayout->addItem( volLayout ); - - guiAddSlidersAndMediacontrols(wantsPlaybackSliders, wantsCaptureSliders, wantsMediaControls, volLayout, tooltipText, captureTooltipText); - if ( !hasVolumeSliders ) - controlLayout->addStretch(1); // Not sure why we have this for "vertical sliders" case - - guiAddCaptureCheckbox(wantsCaptureLED, centerAlign, controlLayout, captureTooltipText); - - // spacer which is shown when no capture button present - captureSpacer = new QWidget(this); - controlLayout->addWidget( captureSpacer ); - captureSpacer->installEventFilter(this); - - - //mute button - guiAddMuteButton(wantsMuteButton, centerAlign, controlLayout, muteTooltipText); + 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); } - else + + m_controlGrid = new QGridLayout(this); + setLayout(m_controlGrid); + QBoxLayout *volLayout; + + if (orientation()==Qt::Vertical) // vertical sliders { - /* - * Horizontal sliders: row1 contains the label (and capture button). - * row2 contains icon, sliders, and mute button - */ + m_controlGrid->setContentsMargins(2, 0, 2, 0); + const Qt::Alignment sliderAlign = Qt::AlignHCenter|Qt::AlignBottom; - QVBoxLayout *rows = new QVBoxLayout( this ); + // Row 0: Control type icon + guiAddControlIcon(tooltipText); + m_controlGrid->addWidget(m_controlIcon, 0, 0, 1, -1, Qt::AlignHCenter|Qt::AlignTop); - // --- ROW1 ------------------------------------------------------------------------ - QHBoxLayout *row1 = new QHBoxLayout(); - rows->addItem( row1 ); + // Row 1: Device name label + guiAddControlLabel(Qt::AlignHCenter, channelName); + m_controlGrid->addWidget(m_controlLabel, 1, 0, 1, -1, Qt::AlignHCenter|Qt::AlignTop); - m_label = new QLabel(m_mixdevice->readableName(), this); - m_label->installEventFilter( this ); - row1->addWidget( m_label ); - row1->setAlignment(m_label, Qt::AlignVCenter); + // Row 2: Sliders - row1->addStretch(); - row1->addWidget(captureSpacer); + int col = 0; // current column being filled + int playbackCol = 0; // where these sliders ended up + int captureCol = 1; // or default button column if none - guiAddCaptureCheckbox(wantsCaptureLED, Qt::AlignRight, row1, captureTooltipText); - captureSpacer = new QWidget(this); // create, but do not add to any layout (not used!) + 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; + } - // --- ROW2 ------------------------------------------------------------------------ - QHBoxLayout *row2 = new QHBoxLayout(); - row2->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); - rows->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); - rows->addItem( row2 ); + if (wantsMediaControls) + { + volLayout = new QHBoxLayout(); + volLayout->setAlignment(sliderAlign); + addMediaControls(volLayout); + m_controlGrid->addLayout(volLayout, 2, col); + } - guiAddControlIcon(Qt::AlignVCenter, row2, tooltipText); + 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); - // --- SLIDERS --------------------------- - QBoxLayout *volLayout = new QVBoxLayout( ); - volLayout->setAlignment(Qt::AlignVCenter|Qt::AlignRight); - row2->addItem( volLayout ); + // Column 2: Sliders - guiAddSlidersAndMediacontrols(wantsPlaybackSliders, wantsCaptureSliders, wantsMediaControls, volLayout, tooltipText, captureTooltipText); - guiAddMuteButton(wantsMuteButton, Qt::AlignRight, row2, muteTooltipText); + 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); } - bool stereoLinked = !_pctl->isSplit(); + const bool stereoLinked = !profileControl()->isSplit(); setStereoLinked( stereoLinked ); - layout()->activate(); // Activate it explicitly in KDE3 because of PanelApplet/kicker issues + // 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()->getMediaController(); QBoxLayout *mediaLayout; - if (_orientation == Qt::Vertical) - mediaLayout = new QVBoxLayout(); - else - mediaLayout = new QHBoxLayout(); + 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); - mediaButton = addMediaButton(mediaIcon, mediaLayout, frame); - connect(mediaButton, SIGNAL(clicked(bool)), this, SLOT(mediaPlay(bool))); + 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->setIconSize(QSize(IconSize(KIconLoader::Toolbar), IconSize(KIconLoader::Toolbar))); lbl->setAutoRaise(true); lbl->setCheckable(false); - setIcon(iconName, lbl); + ToggleToolButton::setIndicatorIcon(iconName, lbl); layout->addWidget(lbl); return lbl; } /** * Updates the icon according to the data model. */ void MDWSlider::updateMediaButton() { - if (mediaButton == 0) + if (m_mediaPlayButton == 0) return; // has no media button MediaController* mediaController = mixDevice()->getMediaController(); QString mediaIconName = calculatePlaybackIcon(mediaController->getPlayState()); - setIcon(mediaIconName, mediaButton); + 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]; + //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, volLayout, true); + subcontrolLabel = createLabel(this, subcontrolTranslation, orientation(), true); + volLayout->addWidget(subcontrolLabel); QAbstractSlider* slider; - if ( m_small ) + if (flags() & MixDeviceWidget::SmallSize) { slider = new KSmallSlider( minvol, maxvol, (maxvol-minvol+1) / Volume::VOLUME_PAGESTEP_DIVISOR, - vol.getVolume( vc.chid ), _orientation, this ); + vol.getVolume( vc.chid ), orientation(), this ); } // small else { - slider = new VolumeSlider( _orientation, this ); + 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 ( ! _pctl->getBackgroundColor().isEmpty() ) { - slider->setStyleSheet("QSlider { background-color: " + _pctl->getBackgroundColor() + " }"); + 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 ); +// 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; } -QWidget* MDWSlider::createLabel(QWidget* parent, QString& label, QBoxLayout *layout, bool small) -{ - QFont qf; - qf.setPointSize(8); - - QWidget* labelWidget; - if (_orientation == Qt::Horizontal) - { - labelWidget = new QLabel(label, parent); - if ( small ) ((QLabel*)labelWidget)->setFont(qf); - } - else { - labelWidget = new VerticalText(parent, label); - if ( small ) ((VerticalText*)labelWidget)->setFont(qf); - } - - labelWidget->installEventFilter( parent ); - layout->addWidget(labelWidget); - - return labelWidget; -} - - -QPixmap MDWSlider::loadIcon( QString filename, KIconLoader::Group group ) -{ - return KIconLoader::global()->loadIcon( filename, group, IconSize(KIconLoader::Toolbar) ); -} - - -//void MDWSlider::setIcon( QString filename, QLabel** label ) -//{ -// if( (*label) == 0 ) -// { -// *label = new QLabel(this); -// installEventFilter( *label ); -// } -// setIcon(filename, *label); -//} - -/** - * Loads the icon with the given iconName in size KIconLoader::Toolbar, and applies it to the label widget. - * The label must be either a QLabel or a QToolButton. - * - * @param label A QToolButton or a QToolButton that will hold the icon. - */ -void MDWSlider::setIcon( QString filename, QWidget* label ) -{ - QPixmap miniDevPM = loadIcon( filename, KIconLoader::Small ); -// QPixmap miniDevPM = loadIcon( filename, KIconLoader::Toolbar); - if ( !miniDevPM.isNull() ) - { - if ( m_small ) - { - // scale icon - QMatrix t; - t = t.scale( 10.0/miniDevPM.width(), 10.0/miniDevPM.height() ); - miniDevPM = miniDevPM.transformed( t ); - label->resize( 10, 10 ); - } // small size - else - { - label->setMinimumSize(IconSize(KIconLoader::Toolbar),IconSize(KIconLoader::Toolbar)); - } - label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - QLabel* lbl = qobject_cast(label); - if ( lbl != 0 ) - { - lbl->setPixmap( miniDevPM ); - lbl->setAlignment(Qt::AlignHCenter | Qt::AlignCenter); - } // QLabel - else - { - QToolButton* tbt = qobject_cast(label); - if ( tbt != 0 ) - { - tbt->setIcon( miniDevPM ); - } // QToolButton - } - } - else - { - qCCritical(KMIX_LOG) << "Pixmap missing. filename=" << filename; - } -} - QString MDWSlider::iconName() { - return m_mixdevice->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_label != 0) m_label->setVisible(value); - if ( m_muteText != 0) m_muteText->setVisible(value); - if ( m_captureText != 0) m_captureText->setVisible(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 has 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_iconLabelSimple != 0 ) { - if ( ( !m_iconLabelSimple->isHidden() ) !=value ) { + if ( m_controlIcon != 0 ) { + if ( ( !m_controlIcon->isHidden() ) !=value ) { if (value) - m_iconLabelSimple->show(); + m_controlIcon->show(); else - m_iconLabelSimple->hide(); + 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 ++; + ++m_waitForSoundSetComplete; volumeValues.push_back(m_slidersPlayback.first()->value()); - volumeChangeInternal(m_mixdevice->playbackVolume(), m_slidersPlayback); + volumeChangeInternal(mixDevice()->playbackVolume(), m_slidersPlayback); } if (!m_slidersCapture.isEmpty()) { - volumeChangeInternal(m_mixdevice->captureVolume(), m_slidersCapture); + volumeChangeInternal(mixDevice()->captureVolume(), m_slidersCapture); } - bool oldViewBlockSignalState = m_view->blockSignals(true); - m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); - m_view->blockSignals(oldViewBlockSignalState); + QSignalBlocker blocker(view()); + mixDevice()->mixer()->commitVolumeChange(mixDevice()); } void MDWSlider::volumeChangeInternal(Volume& vol, QList& ref_sliders) { if (isStereoLinked()) { QAbstractSlider* firstSlider = ref_sliders.first(); - m_mixdevice->setMuted(false); + mixDevice()->setMuted(false); vol.setAllVolumes(firstSlider->value()); } else { for (int i = 0; i < ref_sliders.count(); i++) { - if (m_mixdevice->isMuted()) + if (mixDevice()->isMuted()) { // changing from muted state: unmute (the "if" above is actually superfluous) - m_mixdevice->setMuted(false); + 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( m_mixdevice->isRecSource() ); + setRecsrc( !mixDevice()->isRecSource() ); } -void MDWSlider::setRecsrc(bool value ) +void MDWSlider::setRecsrc(bool value) { - if ( m_mixdevice->captureVolume().hasSwitch() ) + if ( mixDevice()->captureVolume().hasSwitch() ) { - m_mixdevice->setRecSource( value ); - m_mixdevice->mixer()->commitVolumeChange( m_mixdevice ); + 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( !m_mixdevice->isMuted() ); + setMuted( !mixDevice()->isMuted() ); } void MDWSlider::setMuted(bool value) { - if ( m_mixdevice->hasMuteSwitch() ) + if ( mixDevice()->hasMuteSwitch() ) { - m_mixdevice->setMuted( value ); - m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); + 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) { - m_mixdevice->increaseOrDecreaseVolume(decrease, 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 " << m_view->id(); -// bool oldViewBlockSignalState = m_view->blockSignals(true); - m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); -// qCDebug(KMIX_LOG) << "MDWSlider is unblocking signals for " << m_view->id(); -// m_view->blockSignals(oldViewBlockSignalState); +// 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); } void MDWSlider::moveStreamAutomatic() { - m_mixdevice->mixer()->moveStream(m_mixdevice->id(), ""); + mixDevice()->mixer()->moveStream(mixDevice()->id(), ""); } void MDWSlider::moveStream(QString destId) { - m_mixdevice->mixer()->moveStream(m_mixdevice->id(), destId); + 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 || m_mixdevice->hasMuteSwitch() ) - updateInternal(m_mixdevice->playbackVolume(), m_slidersPlayback, m_mixdevice->isMuted() ); - if ( m_slidersCapture.count() != 0 || m_mixdevice->captureVolume().hasSwitch() ) - updateInternal(m_mixdevice->captureVolume(), m_slidersCapture, m_mixdevice->isNotRecSource() ); - if (m_label) + 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_label))) - l->setText(m_mixdevice->readableName()); - else if ((v = dynamic_cast(m_label))) - v->setText(m_mixdevice->readableName()); + 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; iblockSignals( true ); - -// slider->setValue( useVolume ); + QSignalBlocker blocker(slider); // --- Avoid feedback loops START ----------------- - if((volume_index = volumeValues.indexOf(useVolume)) > -1 && --m_waitForSoundSetComplete < 1) + volume_index = volumeValues.indexOf(useVolume); + if (volume_index>-1 && --m_waitForSoundSetComplete<1) { m_waitForSoundSetComplete = 0; volumeValues.removeAt(volume_index); - if(!m_sliderInWork) - slider->setValue(useVolume); + if (!m_sliderInWork) slider->setValue(useVolume); } - else if(!m_sliderInWork && m_waitForSoundSetComplete < 1) + else if (!m_sliderInWork && m_waitForSoundSetComplete<1) { slider->setValue(useVolume); } // --- Avoid feedback loops END ----------------- - if ( slider->inherits( "KSmallSlider" ) ) + KSmallSlider *smallSlider = qobject_cast(slider); + if (smallSlider!=nullptr) // faster than QObject::inherits() { - ((KSmallSlider*)slider)->setGray( m_mixdevice->isMuted() ); + smallSlider->setGray(mixDevice()->isMuted()); } - slider->blockSignals( oldBlockState ); } // for all sliders - // update mute - - if( m_qcb != 0 ) + // update mute state + if (m_muteButton!=nullptr) { - bool oldBlockState = m_qcb->blockSignals( true ); - QString muteIcon = m_mixdevice->isMuted() ? "audio-volume-muted" : "audio-volume-high"; - setIcon(muteIcon, m_qcb); - m_qcb->blockSignals( oldBlockState ); + QSignalBlocker blocker(m_muteButton); + m_muteButton->setActive(!mixDevice()->isMuted()); } - if( m_captureCheckbox ) + // update capture state + if (m_captureButton!=nullptr) { - bool oldBlockState = m_captureCheckbox->blockSignals( true ); - m_captureCheckbox->setChecked( m_mixdevice->isRecSource() ); - m_captureCheckbox->blockSignals( oldBlockState ); + 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 = m_mixdevice->playbackVolume().getVolumes().values(); + QList vols = mixDevice()->playbackVolume().getVolumes().values(); foreach (QAbstractSlider *slider, m_slidersPlayback) { slider->setAccessibleName(slider->toolTip()+ " (" +Volume::channelNameReadable(vols.first().chid)+')'); vols.pop_front(); } - vols = m_mixdevice->captureVolume().getVolumes().values(); + 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 ) +void MDWSlider::showContextMenu(const QPoint &pos) { - if( m_view == 0 ) - return; + if (view()==nullptr) return; - QMenu *menu = m_view->getPopup(); - menu->addSection( SmallIcon( "kmix" ), m_mixdevice->readableName() ); + QMenu *menu = view()->getPopup(); + menu->addSection( SmallIcon( "kmix" ), mixDevice()->readableName() ); if (m_moveMenu) { - MixSet *ms = m_mixdevice->getMoveDestinationMixSet(); + MixSet *ms = mixDevice()->getMoveDestinationMixSet(); Q_ASSERT(ms); m_moveMenu->setEnabled((ms->count() > 1)); menu->addMenu( m_moveMenu ); } if ( m_slidersPlayback.count()>1 || m_slidersCapture.count()>1) { - KToggleAction *stereo = (KToggleAction *)_mdwActions->action( "stereo" ); - if ( stereo ) { - stereo->setChecked( !isStereoLinked() ); + KToggleAction *stereo = qobject_cast(_mdwActions->action("stereo")); + if (stereo!=nullptr) { + QSignalBlocker blocker(stereo); + stereo->setChecked(!isStereoLinked()); menu->addAction( stereo ); } } - if ( m_mixdevice->captureVolume().hasSwitch() ) { - KToggleAction *ta = (KToggleAction *)_mdwActions->action( "recsrc" ); - if ( ta ) { - ta->setChecked( m_mixdevice->isRecSource() ); + 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 ( m_mixdevice->hasMuteSwitch() ) { - KToggleAction *ta = ( KToggleAction* )_mdwActions->action( "mute" ); - if ( ta ) { - ta->setChecked( m_mixdevice->isMuted() ); + 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 ) { -// QAction sep( _mdwPopupActions ); -// sep.setSeparator( true ); -// menu->addAction( &sep ); - menu->addAction( b ); + if (b!=nullptr) + { + menu->addSeparator(); + menu->addAction(b); } - menu->popup( pos ); + menu->popup(pos); } void MDWSlider::showMoveMenu() { - MixSet *ms = m_mixdevice->getMoveDestinationMixSet(); + MixSet *ms = mixDevice()->getMoveDestinationMixSet(); Q_ASSERT(ms); _mdwMoveActions->clear(); m_moveMenu->clear(); // Default QAction *a = new QAction(_mdwMoveActions); a->setText( i18n("Automatic According to Category") ); _mdwMoveActions->addAction( QString("moveautomatic"), a); connect(a, SIGNAL(triggered(bool)), SLOT(moveStreamAutomatic()), Qt::QueuedConnection); m_moveMenu->addAction( a ); a = new QAction(_mdwMoveActions); a->setSeparator(true); _mdwMoveActions->addAction( QString("-"), a); m_moveMenu->addAction( a ); foreach (shared_ptr md, *ms) { a = new MDWMoveAction(md, _mdwMoveActions); _mdwMoveActions->addAction( QString("moveto") + md->id(), a); connect(a, SIGNAL(moveRequest(QString)), SLOT(moveStream(QString)), Qt::QueuedConnection); m_moveMenu->addAction( a ); } } + /** - * An event filter for the various QWidgets. We watch for Mouse press Events, so - * that we can popup the context menu. + * 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* e ) +bool MDWSlider::eventFilter(QObject *obj, QEvent *ev) { - QEvent::Type eventType = e->type(); - if (eventType == QEvent::MouseButtonPress) { - QMouseEvent *qme = static_cast(e); - if (qme->button() == Qt::RightButton) { - showContextMenu(); - return true; - } - } else if (eventType == QEvent::ContextMenu) { - QPoint pos = reinterpret_cast(obj)->mapToGlobal(QPoint(0, 0)); - showContextMenu(pos); - return true; - } - // Attention: We don't filter WheelEvents for KSmallSlider, because it handles WheelEvents itself - else if ( eventType == QEvent::Wheel ) -// && strcmp(obj->metaObject()->className(),"KSmallSlider") != 0 ) { // Remove the KSmallSlider check. If KSmallSlider comes back, use a cheaper type check - e.g. a boolean value. - { - QWheelEvent *qwe = static_cast(e); + if (ev->type()!=QEvent::Wheel) return (QWidget::eventFilter(obj, ev)); + // only want wheel events + if (!ev->spontaneous()) return (false); // avoid recursion on slider - bool increase = (qwe->delta() > 0); - if (qwe->orientation() == Qt::Horizontal) // Reverse horizontal scroll: bko228780 - increase = !increase; + 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); + } - Volume::VolumeTypeFlag volumeType = Volume::Playback; - QAbstractSlider *slider = qobject_cast(obj); - if (slider != 0) - { -// qCDebug(KMIX_LOG); -// qCDebug(KMIX_LOG); -// qCDebug(KMIX_LOG) << "----------------------------- Slider is " << slider; - // Mouse is over a slider. So lets apply the wheel event to playback or capture only - if(m_slidersCapture.contains(slider)) - { -// qCDebug(KMIX_LOG) << "Slider is capture " << slider; - volumeType = Volume::Capture; - } - } - else - { - // Mouse not over a slider => do a little guessing - if (!m_slidersPlayback.isEmpty()) - slider = qobject_cast(m_slidersPlayback.first()); - else if (!m_slidersCapture.isEmpty()) - slider = qobject_cast(m_slidersCapture.first()); - else - slider = 0; - } + // 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; - increaseOrDecreaseVolume(!increase, volumeType); - - if (slider != 0) - { - Volume& volP = m_mixdevice->playbackVolume(); -// qCDebug(KMIX_LOG) << "slider=" << slider->objectName(); - VolumeSliderExtraData& sliderExtraData = extraData(slider); -// qCDebug(KMIX_LOG) << "slider=" << slider->objectName() << "sliderExtraData=" << sliderExtraData.getSubcontrolLabel() << " , chid=" << sliderExtraData.getChid(); - volumeValues.push_back(volP.getVolume(sliderExtraData.getChid())); - } - return true; + if (slider!=nullptr) + { + //qCDebug(KMIX_LOG) << "identified for slider" << slider; + QCoreApplication::sendEvent(slider, ev); } - return QWidget::eventFilter(obj,e); -} + return (true); // wheel event handled +} diff --git a/gui/mdwslider.h b/gui/mdwslider.h index d11211e0..6f7f3850 100644 --- a/gui/mdwslider.h +++ b/gui/mdwslider.h @@ -1,180 +1,160 @@ //-*-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 "volumeslider.h" -#include -#include -#include #include -#include + +#include + +#include "gui/volumeslider.h" +#include "gui/mixdevicewidget.h" +#include "core/volume.h" + class QBoxLayout; +class QGridLayout; class QToolButton; class QLabel; class QMenu; -#include - class MixDevice; class VerticalText; class ViewBase; - -#include "gui/mixdevicewidget.h" -#include "core/volume.h" +class ToggleToolButton; class MDWSlider : public MixDeviceWidget { Q_OBJECT public: - MDWSlider( shared_ptr md, - bool includePlayback, bool includeCapture, - bool includeMixerName, bool small, Qt::Orientation, - QWidget* parent, ViewBase* view, ProfControl *pctl); + 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; -// void setIcon( QString filename, QLabel** label ); - void setIcon( QString filename, QWidget* label ); + 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* e ) 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; - void setLabelExtent(int extent); + int labelExtentHint() const Q_DECL_OVERRIDE; + void setLabelExtent(int extent) Q_DECL_OVERRIDE; bool hasMuteButton() const; - void setMuteButtonSpace(bool); - void setCaptureLEDSpace(bool); bool hasCaptureLED() const; - static VolumeSliderExtraData DummVolumeSliderExtraData; 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 setRecsrc(bool value); void setMuted(bool value); void volumeChange( int ); void sliderPressed(); void sliderReleased(); void increaseVolume(); void decreaseVolume(); void moveStreamAutomatic(); void moveStream( QString destId ); void mediaPlay(bool); void mediaNext(bool); void mediaPrev(bool); private: - QPixmap loadIcon( QString filename, KIconLoader::Group group ); - void createWidgets( bool showMuteLED, bool showCaptureLED, bool includeMixer ); + void createWidgets(); void addSliders( QBoxLayout *volLayout, char type, Volume& vol, QList& ref_sliders, QString tooltipText ); - //void addDefaultLabel(QBoxLayout *layout, Qt::Orientation orientation); // 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 - QWidget* createLabel(QWidget* parent, QString& label, QBoxLayout *layout, bool); QString calculatePlaybackIcon(MediaController::PlayState playState); - void guiAddSlidersAndMediacontrols(bool playSliders, bool capSliders, bool mediaControls, QBoxLayout* layout, const QString& tooltipText, const QString& captureTooltipText); - void guiAddCaptureCheckbox(bool wantsCaptureLED, const Qt::Alignment& alignmentForCapture, - QBoxLayout* layoutForCapture, const QString& captureTooltipText); - void guiAddMuteButton(bool wantsMuteButton, Qt::Alignment alignment, QBoxLayout* layoutForMuteButton, const QString& muteTooltipText); - void guiAddControlIcon(Qt::Alignment alignment, QBoxLayout* layout, const QString& tooltipText); + 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; - QWidget *muteButtonSpacer; - QWidget *captureSpacer; - QWidget *labelSpacer; - - // GUI: Top portion ( Icon + Mute) - QLabel *m_iconLabelSimple; - QToolButton* m_qcb; - QLabel* m_muteText; - - QLabel *m_label; // is either QLabel or VerticalText - QToolButton *mediaButton; + QGridLayout *m_controlGrid; - QCheckBox* m_captureCheckbox; - QLabel* m_captureText; + QLabel *m_controlIcon; + QLabel *m_controlLabel; // is either QLabel or VerticalText - int labelSpacing; - bool muteButtonSpacing; - bool captureLEDSpacing; + ToggleToolButton *m_muteButton; + ToggleToolButton *m_captureButton; + QToolButton *m_mediaPlayButton; + QSize m_controlButtonSize; KActionCollection* _mdwMoveActions; 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 4e6e867f..460ecff8 100644 --- a/gui/mixdevicewidget.cpp +++ b/gui/mixdevicewidget.cpp @@ -1,113 +1,109 @@ /* * 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, - bool small, Qt::Orientation orientation, - QWidget* parent, ViewBase* view, ProfControl* par_pctl) : - QWidget( parent ), m_mixdevice( md ), m_view( view ), _pctl(par_pctl), - _orientation( orientation ), m_small( small ) - , m_shortcutsDialog(0) +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); } } -MixDeviceWidget::~MixDeviceWidget() -{ -} - 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 */ } - - - -void MixDeviceWidget::mousePressEvent( QMouseEvent *e ) -{ - if ( e->button() == Qt::RightButton ) - showContextMenu(); - else { - QWidget::mousePressEvent(e); - } -} - - diff --git a/gui/mixdevicewidget.h b/gui/mixdevicewidget.h index b3b15c80..5df2c200 100644 --- a/gui/mixdevicewidget.h +++ b/gui/mixdevicewidget.h @@ -1,96 +1,108 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> * 1996-2000 Christian Esken * Sven Fischer * * 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 MIXDEVICEWIDGET_H #define MIXDEVICEWIDGET_H -#include #include "core/mixdevice.h" #include "core/volume.h" -#include - +#include "gui/viewbase.h" class KActionCollection; class KShortcutsDialog; class MixDevice; class ProfControl; -class ViewBase; -class MixDeviceWidget - : public QWidget +class MixDeviceWidget : public QWidget { - Q_OBJECT + Q_OBJECT public: - MixDeviceWidget( shared_ptr md, - bool small, Qt::Orientation orientation, - QWidget* parent, ViewBase*, ProfControl * ); - virtual ~MixDeviceWidget(); + enum MDWFlag + { + SmallSize = 0x01, + ShowMute = 0x02, + ShowCapture = 0x04, + ShowMixerName = 0x08 + }; + Q_DECLARE_FLAGS(MDWFlags, MDWFlag); - void addActionToPopup( QAction *action ); + MixDeviceWidget(shared_ptr md, MDWFlags flags, ViewBase *view, ProfControl *pctl); + virtual ~MixDeviceWidget() = default; - shared_ptr mixDevice() { return m_mixdevice; } + shared_ptr mixDevice() const { return (m_mixdevice); } virtual void setColors( QColor high, QColor low, QColor back ); virtual void setIcons( bool value ); virtual void setMutedColors( QColor high, QColor low, QColor back ); - virtual bool isStereoLinked() const { return false; } - virtual void setStereoLinked( bool ) {} - virtual void setLabeled( bool ); - virtual void setTicks( bool ) {} + virtual bool isStereoLinked() const { return (false); } + virtual void setStereoLinked(bool) {} + virtual void setLabeled(bool); + virtual void setTicks(bool) {} + virtual int labelExtentHint() const { return (0); } + virtual void setLabelExtent(int extent) { Q_UNUSED(extent); } public slots: - virtual void defineKeys(); - virtual void showContextMenu( const QPoint &pos = QCursor::pos() ) = 0; - /** - * update() is called whenever there are volume updates pending from the hardware for this MDW. - */ + /** + * Called whenever there are volume updates pending from the hardware for this MDW. + */ virtual void update() = 0; signals: void guiVisibilityChange(MixDeviceWidget* source, bool enable); protected slots: - virtual void setDisabled( bool value ) = 0; - void volumeChange( int ); + virtual void showContextMenu(const QPoint &pos = QCursor::pos()) = 0; + virtual void setDisabled(bool value) = 0; + + virtual void defineKeys(); + void volumeChange(int); protected: + void addActionToPopup(QAction *action); + void contextMenuEvent(QContextMenuEvent *ev) Q_DECL_OVERRIDE; - shared_ptr m_mixdevice; + Qt::Orientation orientation() const { return (m_view->orientation()); } + MixDeviceWidget::MDWFlags flags() const { return (m_flags); } + ViewBase *view() const { return (m_view); } + ProfControl *profileControl() const { return (m_pctl); } + +protected: KActionCollection* _mdwActions; KActionCollection* _mdwPopupActions; - ViewBase* m_view; - ProfControl* _pctl; - Qt::Orientation _orientation; - bool m_small; - KShortcutsDialog* m_shortcutsDialog; private: - void mousePressEvent( QMouseEvent *e ) Q_DECL_OVERRIDE; + MDWFlags m_flags; + shared_ptr m_mixdevice; + ProfControl *m_pctl; + ViewBase *m_view; + KShortcutsDialog *m_shortcutsDialog; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(MixDeviceWidget::MDWFlags); + #endif diff --git a/gui/toggletoolbutton.cpp b/gui/toggletoolbutton.cpp new file mode 100644 index 00000000..79824cd9 --- /dev/null +++ b/gui/toggletoolbutton.cpp @@ -0,0 +1,155 @@ +/* + * 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 + + +static const KIconLoader::Group iconLoadGroup = KIconLoader::Small; +static const KIconLoader::Group iconSizeGroup = KIconLoader::Toolbar; +static const int iconSmallSize = 10; + + +ToggleToolButton::ToggleToolButton(const QString &activeIconName, QWidget *pnt) + : QToolButton(pnt) +{ + mActiveLoaded = mInactiveLoaded = false; + mActiveIcon = activeIconName; + mSmallSize = false; + mIsActive = true; + mFirstTime = true; + + setCheckable(false); + setAutoRaise(true); +} + + +// based on MDWSlider::setIcon() +QPixmap getPixmap(const QString &name, bool small = false) +{ + QPixmap pix = KIconLoader::global()->loadIcon(name, iconLoadGroup, IconSize(iconSizeGroup)); + if (!pix.isNull()) // load icon, check success + { // if wanting small size, scale pixmap + if (small) pix = pix.scaled(iconSmallSize, iconSmallSize); + } + 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 + if (mIsActive) // look at new state + { + if (mActivePixmap.isNull()) // need pixmap for active state + { // only if not already tried + if (!mActiveLoaded) mActivePixmap = getPixmap(mActiveIcon, mSmallSize); + mActiveLoaded = true; // note not to try again + } + + pix = &mActivePixmap; // the pixmap to use + } + else // want inactive state + { + if (mInactivePixmap.isNull()) // need pixmap for inactive state + { + if (!mInactiveIcon.isEmpty()) // inactive icon is set + { + if (!mInactiveLoaded) mInactivePixmap = getPixmap(mInactiveIcon, mSmallSize); + } + else + { // need to derive from active state + if (!mActiveLoaded) mActivePixmap = getPixmap(mActiveIcon, mSmallSize); + mActiveLoaded = true; // only if not already tried + if (mActivePixmap.isNull()) qCWarning(KMIX_LOG) << "want inactive but no active available"; + else + { + mInactivePixmap = KIconLoader::global()->iconEffect()->apply(mActivePixmap, + KIconLoader::Toolbar, + KIconLoader::DisabledState); + } + } + + mInactiveLoaded = true; // note not to try again + } + + pix = &mInactivePixmap; // the pixmap to use + } + + if (pix->isNull()) return; // pixmap not available + setIcon(*pix); // set button pixmap +} + + +/** + * Loads the icon with the given @p iconName in the size KIconLoader::Small, + * 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, small); + if (pix.isNull()) + { + qCWarning(KMIX_LOG) << "Could not get pixmap for" << iconName; + return; + } + + if (small) // small size, set for scaled icon + { + label->resize(iconSmallSize, iconSmallSize); + } + else // not small size, set for normal icon + { + label->resize(IconSize(iconSizeGroup), IconSize(iconSizeGroup)); + } + label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + QLabel *lbl = qobject_cast(label); + if (lbl!=nullptr) + { + lbl->setPixmap(pix); + lbl->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter); + } + else + { + QToolButton *tbt = qobject_cast(label); + if (tbt!=nullptr) tbt->setIcon(pix); // works because implicit QPixmap -> QIcon + } +} diff --git a/gui/toggletoolbutton.h b/gui/toggletoolbutton.h new file mode 100644 index 00000000..a35b5ba5 --- /dev/null +++ b/gui/toggletoolbutton.h @@ -0,0 +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; } + + static void setIndicatorIcon(const QString &iconName, QWidget *label, bool small = false); + +private: + bool mSmallSize; + QString mActiveIcon; + QString mInactiveIcon; + + bool mIsActive; + bool mFirstTime; + + QPixmap mActivePixmap; + bool mActiveLoaded; + QPixmap mInactivePixmap; + bool mInactiveLoaded; +}; + +#endif // TOGGLETOOLBUTTON_H diff --git a/gui/viewbase.cpp b/gui/viewbase.cpp index 6b819ed8..d3930da5 100644 --- a/gui/viewbase.cpp +++ b/gui/viewbase.cpp @@ -1,499 +1,481 @@ /* * 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 - * _setMixSet() in your derived class. + * 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), _guiProfileId(guiProfileId) -, guiLevel(GuiVisibility::GuiSIMPLE) + : 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; - configureIcon = QIcon::fromTheme( QLatin1String( "configure" )); - 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); } } } -ViewBase::~ViewBase() -{ - // Hint: The GUI profile will not be removed, as it is pooled and might be applied to a new View. -} - void ViewBase::addMixer(Mixer *mixer) { _mixers.append(mixer); } -//void ViewBase::configurationUpdate() { -//} - - QPushButton* ViewBase::createConfigureViewButton() { - QPushButton* configureViewButton = new QPushButton(configureIcon, "", this); + 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(); } -QString ViewBase::id() const { - return m_viewId; -} - 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() { - _setMixSet(); - foreach ( shared_ptr md, _mixSet ) + // 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 View implementation do its work - _mdws.append(mdw); // b) Add it to the local list + 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() ) + if (!isDynamic()) { - QAction *action = _localActionColletion->addAction("toggle_channels"); - action->setText(i18n("&Channels")); - connect(action, SIGNAL(triggered(bool)), SLOT(configureView())); - } + QAction *action = _localActionColletion->addAction("toggle_channels"); + action->setText(i18n("Configure Channels...")); + connect(action, SIGNAL(triggered(bool)), SLOT(configureView())); + } - // allow view to "polish" itself - constructionFinished(); + 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::mousePressEvent( QMouseEvent *e ) +// // ---------- Popup stuff START --------------------- + +void ViewBase::contextMenuEvent(QContextMenuEvent *ev) { - if ( e->button() == Qt::RightButton ) - showContextMenu(); + 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 redraw them - while (!_mdws.isEmpty()) - delete _mdws.takeFirst(); + // 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() +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(KConfig *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 GuiVisibility guiVisibilities[3] = - { GuiVisibility::GuiSIMPLE, GuiVisibility::GuiEXTENDED, GuiVisibility::GuiFULL }; + static const GuiVisibility guiVisibilities[3] = + { + GuiVisibility::Simple, + GuiVisibility::Extended, + GuiVisibility::Full + }; bool guiLevelSet = false; - for (int i=0; i<3; ++i) + for (int i = 0; i<3; ++i) { - GuiVisibility& guiCompl = guiVisibilities[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(guiVisibilities[2]); + setGuiLevel(GuiVisibility::Full); } -void ViewBase::setGuiLevel(GuiVisibility& guiLevel) +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) +ProfControl *ViewBase::findMdw(const QString& mdwId, GuiVisibility visibility) const { foreach ( ProfControl* pControl, guiProfile()->getControls() ) { - QRegExp idRegExp(pControl->id); + QRegExp idRegExp(pControl->id()); if ( mdwId.contains(idRegExp) ) { - if (pControl->getVisibility().satisfiesVisibility(visibility)) + 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 0; // not found + } // iterate over all ProfControl entries + return (nullptr); // not found } -/** - * Returns the ProfControl* to the given id. The first found is returned. - * GuiVisibilityis not taken into account. . All ProfControl objects are inspected. - * - * @param id The control ID - * @return The corresponding ProfControl*, or 0 if no match was found - */ -ProfControl* ViewBase::findMdw(const QString& id) -{ - foreach ( ProfControl* pControl, guiProfile()->getControls() ) - { - QRegExp idRegExp(pControl->id); - //qCDebug(KMIX_LOG) << "KMixToolBox::loadView() try match " << (*pControl).id << " for " << mdw->mixDevice()->id(); - if ( id.contains(idRegExp) ) - { - return pControl; - } - } // iterate over all ProfControl entries - - return 0;// not found -} - /* * Saves the View configuration */ -void ViewBase::save(KConfig *config) +void ViewBase::save(KConfig *config) const { - ViewBase *view = this; - QString grp = "View."; - grp += view->id(); + 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 < view->_mdws.count(); ++i) + for (int i = 0; i<_mdws.count(); ++i) { - QWidget *qmdw = view->_mdws[i]; - MixDeviceWidget *mdw = qobject_cast(qmdw); + 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(); } } } - - -// ---------- Popup stuff END --------------------- - diff --git a/gui/viewbase.h b/gui/viewbase.h index 7e098139..500f8b42 100644 --- a/gui/viewbase.h +++ b/gui/viewbase.h @@ -1,171 +1,168 @@ //-*-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 ViewBase_h -#define ViewBase_h +#ifndef VIEWBASE_H +#define VIEWBASE_H // Qt -#include -#include #include #include +#include // KDE #include + class QMenu; +class QContextMenuEvent; class Mixer; class MixDevice; +class MixDeviceWidget; // KMix #include "core/mixdevice.h" #include "core/mixset.h" #include "gui/guiprofile.h" -#include "gui/mixdevicewidget.h" /** - * The ViewBase is a virtual base class, to be used for subclassing the real Mixer Views. + * The ViewBase is an abstract base class, to be used for subclassing the real Mixer Views. */ class ViewBase : public QWidget { Q_OBJECT -friend class KMixToolBox; // the toolbox is everybodys friend :-) - public: + enum ViewFlag + { + HasMenuBar = 0x0001, + MenuBarVisible = 0x0002 + }; + Q_DECLARE_FLAGS(ViewFlags, ViewFlag); - typedef uint ViewFlags; - enum ViewFlagsEnum { - // Regular flags - HasMenuBar = 0x0001, - MenuBarVisible = 0x0002, - Horizontal = 0x0004, - Vertical = 0x0008 - }; - ViewBase(QWidget* parent, QString id, Qt::WindowFlags f, ViewFlags vflags, QString guiProfileId, KActionCollection* actionCollection = 0); - virtual ~ViewBase(); - - void addMixer(Mixer *mixer); - - QString id() const; - - // This method is called by ViewBase at the end of createDeviceWidgets(). The default - // implementation does nothing. Subclasses can override this method for doing final - // touches. This is very much like polish(), but called at an exactly well-known time. - // Also I do not want Views to interfere with polish() - virtual void constructionFinished() = 0; - - /** - * Creates a suitable representation for the given MixDevice. - */ - virtual QWidget* add(shared_ptr) = 0; + // The GUI profile will not be removed on destruction, + // as it is pooled and might be applied to a new View. + virtual ~ViewBase() = default; // This method is called after a configuration update (show/hide controls, split/unsplit). virtual void configurationUpdate() = 0; - void load(KConfig *config); - void save(KConfig *config); + void load(const KConfig *config); + void save(KConfig *config) const; - /** - * Creates the widgets for all supported devices. The default implementation loops - * over the supported MixDevice's and calls add() for each of it. - */ - virtual void createDeviceWidgets(); + QMenu *getPopup(); - int visibleControls(); - bool isDynamic() const; bool pulseaudioPresent() const; - - /** - * Popup stuff - */ - virtual QMenu* getPopup(); - virtual void popupReset(); - virtual void showContextMenu(); - - virtual bool isValid() const; + int visibleControls() const; void setIcons(bool on); void setLabels(bool on); void setTicks(bool on); - GUIProfile* guiProfile() { return GUIProfile::find(_guiProfileId); }; - // GUIComplexity getGuiComplexity() { return guiComplexity; }; - ProfControl* findMdw(const QString& id); - ProfControl* findMdw(const QString& mdwId, GuiVisibility visibility); + bool isValid() const; + QString id() const { return (m_viewId); } - - KActionCollection* actionCollection() { return _actions; }; + GUIProfile* guiProfile() const { return (GUIProfile::find(_guiProfileId)); } + + ProfControl *findMdw(const QString &mdwId, GuiVisibility visibility = GuiVisibility::Default) const; - QList& getMixers() { return _mixers; }; + KActionCollection *actionCollection() const { return (_actions); }; + const QList &getMixers() const { return (_mixers); }; + int mixDeviceCount() const { return (_mdws.count()); } + QWidget *mixDeviceAt(int i) const { return (_mdws.at(i)); } + + Qt::Orientation orientation() const { return (_orientation); } + +private: /** * Contains the widgets for the _mixSet. There is a 1:1 relationship, which means: * _mdws[i] is the Widget for the MixDevice _mixSet[i] - please see ViewBase::createDeviceWidgets(). * Hint: !! The new ViewSurround class shows that a 1:1 relationship does not work in a general scenario. * I actually DID expect this. The solution is unclear yet, probably there will be a virtual mapper method. */ QList _mdws; -protected: - MixSet _mixSet; - QList _mixers; QMenu *_popMenu; KActionCollection* _actions; // -<- application wide action collection + KActionCollection *_localActionColletion; ViewFlags _vflags; + Qt::Orientation _orientation; + GuiVisibility guiLevel; const QString _guiProfileId; - KActionCollection *_localActionColletion; - QIcon configureIcon; + QString m_viewId; + +private: + void setGuiLevel(GuiVisibility guiLevel); + void updateMediaPlaybackIcons(); + void popupReset(); - virtual void _setMixSet() = 0; +protected: + MixSet _mixSet; + QList _mixers; + +protected: void resetMdws(); void updateGuiOptions(); - QPushButton* createConfigureViewButton(); + QPushButton *createConfigureViewButton(); + void addMixer(Mixer *mixer); - void setGuiLevel(GuiVisibility& guiLevel); + virtual void initLayout() = 0; + virtual Qt::Orientation orientationSetting() const = 0; - GuiVisibility guiLevel; + /** + * Creates the widgets for all supported devices. The default implementation loops + * over the supported MixDevice's and calls add() for each of it. + */ + void createDeviceWidgets(); + + /** + * Popup stuff + */ + void contextMenuEvent(QContextMenuEvent *ev) Q_DECL_OVERRIDE; + virtual void showContextMenu(); + + // Creates a suitable representation for the given MixDevice. + virtual QWidget *add(const shared_ptr) = 0; + + // This method is called by ViewBase at the end of createDeviceWidgets(). The default + // implementation does nothing. Subclasses can override this method for doing final + // touches. This is very much like polish(), but called at an exactly well-known time. + // Also I do not want Views to interfere with polish() + virtual void constructionFinished() = 0; public slots: virtual void refreshVolumeLevels(); // TODO remove virtual void configureView(); - void toggleMenuBarSlot(); - -protected slots: - void mousePressEvent( QMouseEvent *e ) Q_DECL_OVERRIDE; signals: void toggleMenuBar(); -private: - QString m_viewId; - void updateMediaPlaybackIcons(); - private slots: void guiVisibilitySlot(MixDeviceWidget* source, bool enable); - + void toggleMenuBarSlot(); }; -#endif +Q_DECLARE_OPERATORS_FOR_FLAGS(ViewBase::ViewFlags); +#endif diff --git a/gui/viewdockareapopup.cpp b/gui/viewdockareapopup.cpp index 88308b80..cfcc9a60 100644 --- a/gui/viewdockareapopup.cpp +++ b/gui/viewdockareapopup.cpp @@ -1,446 +1,435 @@ /* * 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/viewdockareapopup.h" // Qt #include #include #include #include #include #include #include // KDE #include #include // KMix #include "apps/kmix.h" #include "core/mixer.h" #include "core/ControlManager.h" -#include "core/GlobalConfig.h" #include "gui/dialogchoosebackends.h" #include "gui/guiprofile.h" #include "gui/kmixprefdlg.h" #include "gui/mdwslider.h" #include "dialogbase.h" // Restore volume button feature is incomplete => disabling for KDE 4.10 #undef RESTORE_VOLUME_BUTTON -QString ViewDockAreaPopup::InternedString_Star = QString("*"); -QString ViewDockAreaPopup::InternedString_Subcontrols = QString("pvolume,cvolume,pswitch,cswitch"); -ProfControl* ViewDockAreaPopup::MatchAllForSoundMenu = 0; -//ProfControl(ViewDockAreaPopup::InternedString_Star, ViewDockAreaPopup::InternedString_Subcontrols); ViewDockAreaPopup::ViewDockAreaPopup(QWidget* parent, QString id, ViewBase::ViewFlags vflags, QString guiProfileId, KMixWindow *dockW) : ViewBase(parent, id, 0, vflags, guiProfileId), _kmixMainWindow(dockW) { resetRefs(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); /* * Bug 206724: * Following are excerpts of trying to receive key events while this popup is open. * The best I could do with lots of hacks is to get the keyboard events after a mouse-click in the popup. * But such a solution is neither intuitive nor helpful - if one clicks, then usage of keyboard makes not much sense any longer. * I finally gave up on fixing Bug 206724. */ /* releaseKeyboard(); setFocusPolicy(Qt::StrongFocus); setFocus(Qt::TabFocusReason); releaseKeyboard(); mainWindowButton->setFocusPolicy(Qt::StrongFocus); Also implemented the following "event handlers", but they do not show any signs of key presses: keyPressEvent() x11Event() eventFilter() */ foreach ( Mixer* mixer, Mixer::mixers() ) { // Adding all mixers, as we potentially want to show all master controls addMixer(mixer); - // The list will be redone in _setMixSet() with the actual Mixer instances to use + // The list will be redone in initLayout() with the actual Mixer instances to use } restoreVolumeIcon = QIcon::fromTheme(QLatin1String("quickopen-file")); createDeviceWidgets(); // Register listeners for all mixers ControlManager::instance().addListener( QString(), // all mixers ControlManager::GUI|ControlManager::ControlList|ControlManager::Volume|ControlManager::MasterChanged, this, QString("ViewDockAreaPopup")); } ViewDockAreaPopup::~ViewDockAreaPopup() { ControlManager::instance().removeListener(this); delete _layoutMDW; // Hint: optionsLayout and "everything else" is deleted when "delete _layoutMDW" cascades down } void ViewDockAreaPopup::controlsChange(ControlManager::ChangeType changeType) { switch (changeType) { case ControlManager::ControlList: case ControlManager::MasterChanged: createDeviceWidgets(); break; case ControlManager::GUI: updateGuiOptions(); break; case ControlManager::Volume: refreshVolumeLevels(); break; default: ControlManager::warnUnexpectedChangeType(changeType, this); break; } } -void ViewDockAreaPopup::wheelEvent ( QWheelEvent * e ) -{ - if ( _mdws.isEmpty() ) - return; -} - - void ViewDockAreaPopup::configurationUpdate() { // TODO Do we still need configurationUpdate(). It was never implemented for ViewDockAreaPopup } // TODO Currently no right-click, as we have problems to get the ViewDockAreaPopup resized void ViewDockAreaPopup::showContextMenu() { // no right-button-menu on "dock area popup" return; } void ViewDockAreaPopup::resetRefs() { seperatorBetweenMastersAndStreams = 0; separatorBetweenMastersAndStreamsInserted = false; separatorBetweenMastersAndStreamsRequired = false; configureViewButton = 0; restoreVolumeButton1 = 0; restoreVolumeButton2 = 0; restoreVolumeButton3 = 0; restoreVolumeButton4 = 0; mainWindowButton = 0; optionsLayout = 0; _layoutMDW = 0; } -void ViewDockAreaPopup::_setMixSet() +void ViewDockAreaPopup::initLayout() { resetMdws(); if (optionsLayout != 0) { QLayoutItem *li2; while ((li2 = optionsLayout->takeAt(0))) delete li2; } // Hint : optionsLayout itself is deleted when "delete _layoutMDW" cascades down if (_layoutMDW != 0) { QLayoutItem *li; while ((li = _layoutMDW->takeAt(0))) delete li; } /* * Strangely enough, I cannot delete optionsLayout in a loop. I get a strange stacktrace: * Application: KMix (kmix), signal: Segmentation fault [...] #6 0x00007f9c9a282900 in QString::shared_null () from /usr/lib/x86_64-linux-gnu/libQtCore.so.4 -#7 0x00007f9c9d4286b0 in ViewDockAreaPopup::_setMixSet (this=0x1272b60) at /home/chris/workspace/kmix-git-trunk/gui/viewdockareapopup.cpp:164 +#7 0x00007f9c9d4286b0 in ViewDockAreaPopup::initLayout (this=0x1272b60) at /home/chris/workspace/kmix-git-trunk/gui/viewdockareapopup.cpp:164 #8 0x00007f9c9d425700 in ViewBase::createDeviceWidgets (this=0x1272b60) at /home/chris/workspace/kmix-git-trunk/gui/viewbase.cpp:137 #9 0x00007f9c9d42845b in ViewDockAreaPopup::controlsChange (this=0x1272b60, changeType=2) at /home/chris/workspace/kmix-git-trunk/gui/viewdockareapopup.cpp:91 */ // if ( optionsLayout != 0 ) // { // QLayoutItem *li2; // while ( ( li2 = optionsLayout->takeAt(0) ) ) // strangely enough, it crashes here // delete li2; // } // --- Due to the strange crash, delete everything manually : START --------------- // I am a bit confused why this doesn't crash. I moved the "optionsLayout->takeAt(0) delete" loop at the beginning, // so the objects should already be deleted. ... delete configureViewButton; delete restoreVolumeButton1; delete restoreVolumeButton2; delete restoreVolumeButton3; delete restoreVolumeButton4; delete mainWindowButton; delete seperatorBetweenMastersAndStreams; // --- Due to the strange crash, delete everything manually : END --------------- resetRefs(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); /* * BKO 299754: Looks like I need to explicitly delete layout(). I have no idea why * "delete _layoutMDW" is not enough, as that is supposed to be the layout * of this ViewDockAreaPopup * (Hint: it might have been 0 already. Nowadays it is definitely, see #resetRefs()) */ delete layout(); // BKO 299754 _layoutMDW = new QGridLayout(this); _layoutMDW->setSpacing(DialogBase::verticalSpacing()); // Review #121166: Add some space over device icons, otherwise they may hit window border _layoutMDW->setContentsMargins(0,5,0,0); //_layoutMDW->setSizeConstraint(QLayout::SetMinimumSize); _layoutMDW->setSizeConstraint(QLayout::SetMaximumSize); _layoutMDW->setObjectName(QLatin1String("KmixPopupLayout")); setLayout(_layoutMDW); // Adding all mixers, as we potentially want to show all master controls. Due to hotplugging - // we have to redo the list on each _setMixSet() (instead of setting it once in the Constructor) + // we have to redo the list on each initLayout() (instead of setting it once in the Constructor) _mixers.clear(); QSet preferredMixersForSoundmenu = GlobalConfig::instance().getMixersForSoundmenu(); // qCDebug(KMIX_LOG) << "Launch with " << preferredMixersForSoundmenu; foreach ( Mixer* mixer, Mixer::mixers() ) { bool useMixer = preferredMixersForSoundmenu.isEmpty() || preferredMixersForSoundmenu.contains(mixer->id()); if (useMixer) addMixer(mixer); } // The following loop is for the case when everything gets filtered out. We "reset" to show everything then. // Hint: Filtering everything out can only be an "accident", e.g. when restarting KMix with changed hardware or // backends. if ( _mixers.isEmpty() ) { foreach ( Mixer* mixer, Mixer::mixers() ) { addMixer(mixer); } } // A loop that adds the Master control of each card foreach ( Mixer* mixer, _mixers ) { // qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id(); shared_ptrdockMD = mixer->getLocalMasterMD(); if ( !dockMD && mixer->size() > 0 ) { // If we have no dock device yet, we will take the first available mixer device. dockMD = (*mixer)[0]; } if ( dockMD ) { // qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id(); if ( !dockMD->isApplicationStream() && dockMD->playbackVolume().hasVolume()) { // qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id() << ": YES"; // don't add application streams here. They are handled below, so // we make sure to not add them twice _mixSet.append(dockMD); } } } // loop over all cards // Add all application streams foreach ( Mixer* mixer2 , _mixers ) { foreach ( shared_ptr md, mixer2->getMixSet() ) { if (md->isApplicationStream()) { _mixSet.append(md); } } } } QWidget* ViewDockAreaPopup::add(shared_ptr md) { - bool vertical = (GlobalConfig::instance().data.getTraypopupOrientation() == Qt::Vertical); // I am wondering whether using vflags for this would still make sense + const bool vertical = (orientation()==Qt::Vertical); + /* QString dummyMatchAll("*"); QString matchAllPlaybackAndTheCswitch("pvolume,cvolume,pswitch,cswitch"); // Leak | relevant | pctl Each time a stream is added, a new ProfControl gets created. // It cannot be deleted in ~MixDeviceWidget, as ProfControl* ownership is not consistent. // here a new pctl is created (could be deleted), but in ViewSliders the ProcControl is taken from the // MixDevice, which in turn uses it from the GUIProfile. // Summarizing: ProfControl* is either owned by the GUIProfile or created new (ownership unclear). // Hint: dummyMatchAll and matchAllPlaybackAndTheCswitch leak together with pctl ProfControl *pctl = new ProfControl( dummyMatchAll, matchAllPlaybackAndTheCswitch); */ if ( !md->isApplicationStream() ) { separatorBetweenMastersAndStreamsRequired = true; } if ( !separatorBetweenMastersAndStreamsInserted && separatorBetweenMastersAndStreamsRequired && md->isApplicationStream() ) { // First application stream => add separator separatorBetweenMastersAndStreamsInserted = true; int sliderColumn = vertical ? _layoutMDW->columnCount() : _layoutMDW->rowCount(); int row = vertical ? 0 : sliderColumn; int col = vertical ? sliderColumn : 0; seperatorBetweenMastersAndStreams = new QFrame(this); if (vertical) seperatorBetweenMastersAndStreams->setFrameStyle(QFrame::VLine); else seperatorBetweenMastersAndStreams->setFrameStyle(QFrame::HLine); _layoutMDW->addWidget( seperatorBetweenMastersAndStreams, row, col ); //_layoutMDW->addItem( new QSpacerItem( 5, 5 ), row, col ); } - if (MatchAllForSoundMenu == 0) + static ProfControl *MatchAllForSoundMenu = nullptr; + if (MatchAllForSoundMenu==nullptr) { - // Lazy init of static member on first use - MatchAllForSoundMenu = new ProfControl(ViewDockAreaPopup::InternedString_Star, ViewDockAreaPopup::InternedString_Subcontrols); + // Lazy init of static member on first use + MatchAllForSoundMenu = new ProfControl("*", "pvolume,cvolume,pswitch,cswitch"); } - ProfControl *pctl = MatchAllForSoundMenu; - - MixDeviceWidget *mdw = new MDWSlider( - md, // only 1 device. - true, // Show Mute LE - true, // Show Record LED - true, // Include Mixer Name - false, // Small - vertical ? Qt::Vertical : Qt::Horizontal, - this, // parent - this // NOT ANYMORE!!! -> Is "NULL", so that there is no RMB-popup - , pctl - ); + + MixDeviceWidget *mdw = new MDWSlider(md, + MixDeviceWidget::ShowMute|MixDeviceWidget::ShowCapture|MixDeviceWidget::ShowMixerName, + this, + MatchAllForSoundMenu); mdw->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); int sliderColumn = vertical ? _layoutMDW->columnCount() : _layoutMDW->rowCount(); //if (sliderColumn == 1 ) sliderColumn =0; int row = vertical ? 0 : sliderColumn; int col = vertical ? sliderColumn : 0; _layoutMDW->addWidget( mdw, row, col ); //qCDebug(KMIX_LOG) << "ADDED " << md->id() << " at column " << sliderColumn; return mdw; } void ViewDockAreaPopup::constructionFinished() { // qCDebug(KMIX_LOG) << "ViewDockAreaPopup::constructionFinished()\n"; mainWindowButton = new QPushButton(QIcon::fromTheme("show-mixer"), "" , this); mainWindowButton->setObjectName(QLatin1String("MixerPanel")); mainWindowButton->setToolTip(i18n("Show the full mixer window")); connect(mainWindowButton, SIGNAL(clicked()), SLOT(showPanelSlot())); configureViewButton = createConfigureViewButton(); optionsLayout = new QHBoxLayout(); optionsLayout->addWidget(mainWindowButton); optionsLayout->addStretch(1); optionsLayout->addWidget(configureViewButton); #ifdef RESTORE_VOLUME_BUTTON restoreVolumeButton1 = createRestoreVolumeButton(1); optionsLayout->addWidget( restoreVolumeButton1 ); // TODO enable only if user has saved a volume profile // optionsLayout->addWidget( createRestoreVolumeButton(2) ); // optionsLayout->addWidget( createRestoreVolumeButton(3) ); // optionsLayout->addWidget( createRestoreVolumeButton(4) ); #endif int sliderRow = _layoutMDW->rowCount(); _layoutMDW->addLayout(optionsLayout, sliderRow, 0, 1, _layoutMDW->columnCount()); updateGuiOptions(); _layoutMDW->update(); _layoutMDW->activate(); //bool fnc = focusNextChild(); //qCWarning(KMIX_LOG) << "fnc=" <setToolTip(i18n("Load volume profile %1", storageSlot)); profileButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); return profileButton; } + void ViewDockAreaPopup::refreshVolumeLevels() { - foreach ( QWidget* qw, _mdws ) - { - //qCDebug(KMIX_LOG) << "rvl: " << qw; - MixDeviceWidget* mdw = qobject_cast(qw); - if ( mdw != 0 ) mdw->update(); - } + const int num = mixDeviceCount(); + for (int i = 0; i(mixDeviceAt(i)); + if (mdw!=nullptr) mdw->update(); + } } + void ViewDockAreaPopup::configureView() { // Q_ASSERT( !pulseaudioPresent() ); // QSet currentlyActiveMixersInDockArea; // foreach ( Mixer* mixer, _mixers ) // { // currentlyActiveMixersInDockArea.insert(mixer->id()); // } KMixPrefDlg* prefDlg = KMixPrefDlg::getInstance(); //prefDlg->setActiveMixersInDock(currentlyActiveMixersInDockArea); prefDlg->switchToPage(KMixPrefDlg::PrefSoundMenu); } /** * This gets activated whne a user clicks the "Mixer" PushButton in this popup. */ void ViewDockAreaPopup::showPanelSlot() { _kmixMainWindow->setVisible(true); KWindowSystem::setOnDesktop(_kmixMainWindow->winId(), KWindowSystem::currentDesktop()); KWindowSystem::activateWindow(_kmixMainWindow->winId()); // This is only needed when the window is already visible. static_cast(parent())->hide(); } + +Qt::Orientation ViewDockAreaPopup::orientationSetting() const +{ + return (GlobalConfig::instance().data.getTraypopupOrientation()); +} diff --git a/gui/viewdockareapopup.h b/gui/viewdockareapopup.h index 55cc50a5..2b8bbdaa 100644 --- a/gui/viewdockareapopup.h +++ b/gui/viewdockareapopup.h @@ -1,89 +1,89 @@ //-*-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 ViewDockAreaPopup_h #define ViewDockAreaPopup_h #include "viewbase.h" #include #include #include "core/ControlManager.h" class QBoxLayout; class QFrame; class QGridLayout; class QWidget; class Mixer; class MixDevice; class KMixWindow; class ViewDockAreaPopup : public ViewBase { Q_OBJECT public: ViewDockAreaPopup(QWidget* parent, QString id, ViewBase::ViewFlags vflags, QString guiProfileId, KMixWindow *dockW); virtual ~ViewDockAreaPopup(); QWidget* add(shared_ptr md) Q_DECL_OVERRIDE; void constructionFinished() Q_DECL_OVERRIDE; void refreshVolumeLevels() Q_DECL_OVERRIDE; void showContextMenu() Q_DECL_OVERRIDE; void configurationUpdate() Q_DECL_OVERRIDE; protected: KMixWindow *_kmixMainWindow; - void wheelEvent ( QWheelEvent * e ) Q_DECL_OVERRIDE; - void _setMixSet() Q_DECL_OVERRIDE; + void initLayout() Q_DECL_OVERRIDE; + Qt::Orientation orientationSetting() const Q_DECL_OVERRIDE; private: QGridLayout* _layoutMDW; QPushButton* createRestoreVolumeButton ( int storageSlot ); bool separatorBetweenMastersAndStreamsInserted; bool separatorBetweenMastersAndStreamsRequired; QFrame* seperatorBetweenMastersAndStreams; QBoxLayout* optionsLayout; QPushButton* configureViewButton; QPushButton *mainWindowButton; QPushButton *restoreVolumeButton1; QPushButton *restoreVolumeButton2; QPushButton *restoreVolumeButton3; QPushButton *restoreVolumeButton4; QIcon restoreVolumeIcon; static ProfControl* MatchAllForSoundMenu; static QString InternedString_Star; static QString InternedString_Subcontrols; public slots: void controlsChange(ControlManager::ChangeType changeType); void configureView() Q_DECL_OVERRIDE; private slots: void showPanelSlot(); void resetRefs(); }; #endif diff --git a/gui/viewsliders.cpp b/gui/viewsliders.cpp index 3eaceee8..c2eae30d 100644 --- a/gui/viewsliders.cpp +++ b/gui/viewsliders.cpp @@ -1,445 +1,418 @@ /* * 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. */ //#define TEST_MIXDEVICE_COMPOSITE #undef TEST_MIXDEVICE_COMPOSITE #ifdef TEST_MIXDEVICE_COMPOSITE #ifdef __GNUC__ #warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #warning !!! MIXDEVICE COMPOSITE TESTING IS ACTIVATED !!! #warning !!! THIS IS PRE-ALPHA CODE! !!! #warning !!! DO NOT SHIP KMIX IN THIS STATE !!! #warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #endif #endif -#include "gui/viewsliders.h" +#include "viewsliders.h" // KMix #include "core/ControlManager.h" -#include "core/GlobalConfig.h" #include "core/mixdevicecomposite.h" #include "core/mixer.h" #include "gui/guiprofile.h" #include "gui/mdwenum.h" #include "gui/mdwslider.h" -#include "gui/verticaltext.h" - // KDE #include +#include // Qt -#include #include -#include -#include -#include #include #include +#include /** * Generic View implementation. This can hold now all kinds of controls (not just Sliders, as * the class name suggests). */ -ViewSliders::ViewSliders(QWidget* parent, QString id, Mixer* mixer, ViewBase::ViewFlags vflags, QString guiProfileId, - KActionCollection *actColl) : - ViewBase(parent, id, Qt::FramelessWindowHint, vflags, guiProfileId, actColl), _layoutEnum(0) +ViewSliders::ViewSliders(QWidget *parent, const QString &id, Mixer *mixer, + ViewBase::ViewFlags vflags, const QString &guiProfileId, + KActionCollection *actColl) + : ViewBase(parent, id, Qt::FramelessWindowHint, vflags, guiProfileId, actColl) { addMixer(mixer); - _configureViewButton = 0; - _layoutMDW = 0; - _layoutSliders = 0; - _layoutEnum = 0; - emptyStreamHint = 0; + m_configureViewButton = nullptr; + m_layoutMDW = nullptr; + m_layoutSliders = nullptr; + m_layoutSwitches = nullptr; + m_emptyStreamHint = nullptr; createDeviceWidgets(); ControlManager::instance().addListener(mixer->id(), ControlManager::GUI|ControlManager::ControlList|ControlManager::Volume, this, QString("ViewSliders.%1").arg(mixer->id())); } ViewSliders::~ViewSliders() { ControlManager::instance().removeListener(this); - delete _layoutMDW; -// qDeleteAll(_separators); + delete m_layoutMDW; } void ViewSliders::controlsChange(ControlManager::ChangeType changeType) { switch (changeType) { case ControlManager::ControlList: createDeviceWidgets(); break; case ControlManager::GUI: updateGuiOptions(); break; case ControlManager::Volume: if (GlobalConfig::instance().data.debugVolume) qCDebug(KMIX_LOG) << "NOW I WILL REFRESH VOLUME LEVELS. I AM " << id(); refreshVolumeLevels(); break; default: ControlManager::warnUnexpectedChangeType(changeType, this); break; } } -QWidget* ViewSliders::add(shared_ptr md) +QWidget *ViewSliders::add(const shared_ptr md) { MixDeviceWidget *mdw; - Qt::Orientation orientation = GlobalConfig::instance().data.getToplevelOrientation(); - if ( md->isEnum() ) + if (md->isEnum()) // control is a switch { - mdw = new MDWEnum( - md, // MixDevice (parameter) - orientation, // Orientation - this, // parent - this // View widget - , md->controlProfile() - ); -// if ( _layoutEnum == 0 ) { -// // lazily creation of Layout for the first enum -// _layoutEnum = new QVBoxLayout(); -// _layoutMDW->addLayout( _layoutEnum ); -// } - _layoutEnum->addWidget(mdw); - } // an enum - else + mdw = new MDWEnum(md, 0, this); + m_layoutSwitches->addWidget(mdw); + } + else // control is a slider { - mdw = new MDWSlider( - md, // MixDevice (parameter) - true, // Show Mute LED - true, // Show Record LED - false, // Include Mixer Name - false, // Small - orientation, // Orientation - this, // parent - this - , md->controlProfile() - ); // View widget - _layoutSliders->addWidget(mdw); + mdw = new MDWSlider(md, MixDeviceWidget::ShowMute|MixDeviceWidget::ShowCapture, this); + m_layoutSliders->addWidget(mdw); } - return mdw; + return (mdw); } -void ViewSliders::_setMixSet() +void ViewSliders::initLayout() { resetMdws(); - delete emptyStreamHint; - emptyStreamHint = 0; - - // Our _layoutSliders now should only contain spacer widgets from the addSpacing() calls in add() above. + // Our m_layoutSliders now should only contain spacer widgets from the addSpacing() calls in add() above. // We need to trash those too otherwise all sliders gradually migrate away from the edge :p - if (_layoutSliders != 0) + if (m_layoutSliders!=nullptr) { QLayoutItem *li; - while ((li = _layoutSliders->takeAt(0))) - delete li; -// delete _layoutSliders; - _layoutSliders = 0; + while ((li = m_layoutSliders->takeAt(0))!=nullptr) delete li; + m_layoutSliders = nullptr; } - delete _configureViewButton; - _configureViewButton = 0; + delete m_configureViewButton; + m_configureViewButton = nullptr; - if (_layoutEnum != 0) + if (m_layoutSwitches!=nullptr) { - QLayoutItem *li2; - while ((li2 = _layoutEnum->takeAt(0)) != 0) - delete li2; -// delete _layoutEnum; - _layoutEnum = 0; + QLayoutItem *li; + while ((li = m_layoutSwitches->takeAt(0))!=nullptr) delete li; + m_layoutSwitches = nullptr; } - delete _layoutMDW; - _layoutMDW = 0; - - // We will be recreating our sliders, so make sure we trash all the separators too. - //qDeleteAll(_separators); - _separators.clear(); + delete m_layoutMDW; + m_layoutMDW = new QGridLayout(this); + m_layoutMDW->setContentsMargins(0, 0, 0, 0); + m_layoutMDW->setSpacing(0); + m_layoutMDW->setRowStretch(0, 1); + m_layoutMDW->setColumnStretch(0, 1); - if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Horizontal) + if (orientation()==Qt::Horizontal) // horizontal sliders { - // Horizontal slider => put them vertically - _layoutMDW = new QVBoxLayout(this); - _layoutMDW->setAlignment(Qt::AlignLeft | Qt::AlignTop); - _layoutSliders = new QVBoxLayout(); - _layoutSliders->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); + m_layoutMDW->setAlignment(Qt::AlignLeft|Qt::AlignTop); + + m_layoutSliders = new QVBoxLayout(); + m_layoutSliders->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); } - else + else // vertical sliders { - // Vertical slider => put them horizontally - _layoutMDW = new QHBoxLayout(this); - _layoutMDW->setAlignment(Qt::AlignHCenter | Qt::AlignTop); - _layoutSliders = new QHBoxLayout(); - _layoutSliders->setAlignment(Qt::AlignHCenter | Qt::AlignTop); - } - _layoutSliders->setContentsMargins(0, 0, 0, 0); - _layoutSliders->setSpacing(0); - _layoutMDW->setContentsMargins(0, 0, 0, 0); - _layoutMDW->setSpacing(0); + m_layoutMDW->setAlignment(Qt::AlignHCenter|Qt::AlignTop); - // The stretch added here is so that, if there is width to spare, the - // controls are centered in the window but the configure button is - // always at the bottom right. - _layoutMDW->addStretch(); - _layoutMDW->addItem(_layoutSliders); - _layoutMDW->addStretch(); + m_layoutSliders = new QHBoxLayout(); + m_layoutSliders->setAlignment(Qt::AlignHCenter|Qt::AlignTop); + } + m_layoutSliders->setContentsMargins(0, 0, 0, 0); + m_layoutSliders->setSpacing(0); - _layoutEnum = new QVBoxLayout(); - _layoutMDW->addLayout(_layoutEnum); + m_layoutSwitches = new QVBoxLayout(); + QString emptyStreamText = i18n("Nothing is playing audio."); // Hint: This text comparison is not a clean solution, but one that will work for quite a while. - QString viewId(id()); - if (viewId.contains(".Capture_Streams.")) - emptyStreamHint = new QLabel(i18n("Nothing is capturing audio.")); - else if (viewId.contains(".Playback_Streams.")) - emptyStreamHint = new QLabel(i18n("Nothing is playing audio.")); - else if (viewId.contains(".Capture_Devices.")) - emptyStreamHint = new QLabel(i18n("No capture devices.")); - else if (viewId.contains(".Playback_Devices.")) - emptyStreamHint = new QLabel(i18n("No playback devices.")); - else - emptyStreamHint = new QLabel(i18n("Nothing is playing audio.")); // Fallback. Assume Playback stream - - emptyStreamHint->setAlignment(Qt::AlignCenter); - emptyStreamHint->setWordWrap(true); - emptyStreamHint->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); - _layoutMDW->addWidget(emptyStreamHint); + const QString viewId = id(); + if (viewId.contains(".Capture_Streams.")) emptyStreamText = i18n("Nothing is capturing audio."); + else if (viewId.contains(".Capture_Devices.")) emptyStreamText = i18n("There are no capture devices."); + else if (viewId.contains(".Playback_Devices.")) emptyStreamText = i18n("There are no playback devices."); + + delete m_emptyStreamHint; + m_emptyStreamHint = new KMessageWidget(emptyStreamText, this); + m_emptyStreamHint->setIcon(QIcon::fromTheme("dialog-information")); + m_emptyStreamHint->setMessageType(KMessageWidget::Information); + m_emptyStreamHint->setCloseButtonVisible(false); + m_layoutSliders->addWidget(m_emptyStreamHint); + + if (orientation()==Qt::Horizontal) // horizontal sliders + { + // Row 0: Sliders + m_layoutMDW->addLayout(m_layoutSliders, 0, 0, 1, -1, Qt::AlignHCenter|Qt::AlignVCenter); + // Row 1: Switches + m_layoutMDW->addLayout(m_layoutSwitches, 1, 0, Qt::AlignLeft|Qt::AlignTop); + } + else // vertical sliders + { + // Column 0: Sliders + m_layoutMDW->addLayout(m_layoutSliders, 0, 0, -1, 1, Qt::AlignHCenter|Qt::AlignVCenter); + // Column 1: Switches + m_layoutMDW->addLayout(m_layoutSwitches, 0, 1, Qt::AlignLeft|Qt::AlignTop); + } #ifdef TEST_MIXDEVICE_COMPOSITE - QList > mds; // For temporary test + QList > mds; // For temporary test #endif - // This method iterates the controls from the Profile + // This method iterates over all the controls from the GUI profile. // Each control is checked, whether it is also contained in the mixset, and // applicable for this kind of View. If yes, the control is accepted and inserted. - GUIProfile* guiprof = guiProfile(); - if (guiprof != 0) + const GUIProfile *guiprof = guiProfile(); + if (guiprof!=nullptr) { - foreach (Mixer* mixer , _mixers ){ - const MixSet& mixset = mixer->getMixSet(); - - foreach ( ProfControl* control, guiprof->getControls() ) + foreach (Mixer *mixer, _mixers) { - //ProfControl* control = *it; - // The TabName of the control matches this View name (!! attention: Better use some ID, due to i18n() ) - QRegExp idRegexp(control->id); - //bool isExactRegexp = control->id.startsWith('^') && control->id.endsWith('$'); // for optimizing - //isExactRegexp &= ( ! control->id.contains(".*") ); // For now. Might be removed in the future, as it cannot be done properly !!! - //qCDebug(KMIX_LOG) << "ViewSliders::setMixSet(): Check GUIProfile id==" << control->id << "\n"; - // The following for-loop could be simplified by using a std::find_if - for ( int i=0; i md = mixset[i]; + const MixSet &mixset = mixer->getMixSet(); - if ( md->id().contains(idRegexp) ) + foreach (ProfControl *control, guiprof->getControls()) + { + // The TabName of the control matches this View name (!! attention: Better use some ID, due to i18n() ) + QRegExp idRegexp(control->id()); + // The following for-loop could be simplified by using a std::find_if + for (int i = 0; iuseSubcontrolPlayback() && ( md->playbackVolume().hasVolume() || md->hasMuteSwitch()) ); - bool subcontrolCaptureWanted = (control->useSubcontrolCapture() && ( md->captureVolume() .hasVolume() || md->captureVolume() .hasSwitch()) ); - bool subcontrolEnumWanted = (control->useSubcontrolEnum() && md->isEnum()); - bool subcontrolWanted = subcontrolPlaybackWanted | subcontrolCaptureWanted | subcontrolEnumWanted; - - if ( !subcontrolWanted ) - { - continue; - } - - md->setControlProfile(control); - if ( !control->name.isNull() ) - { - // Apply the custom name from the profile - md->setReadableName(control->name);// @todo: This is the wrong place. It only applies to controls in THIS type of view - } - if ( !control->getSwitchtype().isNull() ) - { - if ( control->getSwitchtype() == "On" ) - md->playbackVolume().setSwitchType(Volume::OnSwitch); - else if ( control->getSwitchtype() == "Off" ) - md->playbackVolume().setSwitchType(Volume::OffSwitch); - } - _mixSet.append(md); + const shared_ptr md = mixset[i]; + + if (md->id().contains(idRegexp)) + { // match found (by name) + if (_mixSet.contains(md)) + { // check for duplicate already + continue; + } + + // Now check whether subcontrols required + const bool subcontrolPlaybackWanted = (control->useSubcontrolPlayback() && (md->playbackVolume().hasVolume() || md->hasMuteSwitch())); + const bool subcontrolCaptureWanted = (control->useSubcontrolCapture() && (md->captureVolume().hasVolume() || md->captureVolume().hasSwitch())); + const bool subcontrolEnumWanted = (control->useSubcontrolEnum() && md->isEnum()); + const bool subcontrolWanted = subcontrolPlaybackWanted || subcontrolCaptureWanted || subcontrolEnumWanted; + if (!subcontrolWanted) continue; + + md->setControlProfile(control); + if (!control->name().isNull()) + { + // Apply the custom name from the profile + // TODO: This is the wrong place. It only + // applies to controls in THIS type of view. + md->setReadableName(control->name()); + } + if (!control->getSwitchtype().isNull()) + { + if (control->getSwitchtype()=="On") + md->playbackVolume().setSwitchType(Volume::OnSwitch); + else if (control->getSwitchtype()=="Off") + md->playbackVolume().setSwitchType(Volume::OffSwitch); + } + _mixSet.append(md); #ifdef TEST_MIXDEVICE_COMPOSITE - if ( md->id() == "Front:0" || md->id() == "Surround:0") - { mds.append(md);} // For temporary test + if ( md->id() == "Front:0" || md->id() == "Surround:0") + { // For temporary test + mds.append(md); + } #endif - // We use no "break;" ,as multiple devices could match the regexp (e.g. "^.*$") - } // name matches - } // loop for finding a suitable MixDevice - } // iteration over all controls from the Profile - } // if there is a profile -} // Iteration over all Mixers - - emptyStreamHint->setVisible(_mixSet.isEmpty() && isDynamic()); // show a hint why a tab is empty (dynamic controls!!!) - // visibleControls() == 0 could be used for the !isDynamic() case + // No 'break' here, because multiple devices could match + // the regexp (e.g. "^.*$") + } // name matches + } // loop for finding a suitable MixDevice + } // iteration over all controls from the Profile + } // iteration over all Mixers + } // if there is a profile + + // Show a hint why a tab is empty (dynamic controls!) + // TODO: 'visibleControls()==0' could be used for the !isDynamic() case + m_emptyStreamHint->setVisible(_mixSet.isEmpty() && isDynamic()); #ifdef TEST_MIXDEVICE_COMPOSITE // @todo: This is currently hardcoded, and instead must be read as usual from the Profile MixDeviceComposite *mdc = new MixDeviceComposite(_mixer, "Composite_Test", mds, "A Composite Control #1", MixDevice::KMIX_COMPOSITE); Volume::ChannelMask chn = Volume::MMAIN; Volume* vol = new Volume( chn, 0, 100, true, true); mdc->addPlaybackVolume(*vol); QString ctlId("Composite_Test"); QString ctlMatchAll("*"); ProfControl* pctl = new ProfControl(ctlId, ctlMatchAll); mdc->setControlProfile(pctl); _mixSet->append(mdc); #endif } -void ViewSliders::constructionFinished() { - configurationUpdate(); - //if ( !pulseaudioPresent() ) // TODO 11 Dynamic view configuration - if ( !isDynamic() ) - { - _configureViewButton = createConfigureViewButton(); - _layoutEnum->addStretch(); - _layoutEnum->addWidget(_configureViewButton); - } +void ViewSliders::constructionFinished() +{ + m_layoutSwitches->addStretch(1); // push switches to top or left + + configurationUpdate(); + if (!isDynamic()) + { + // Layout row 1 column 1: Configure View button + // TODO: does this need to be a member? + m_configureViewButton = createConfigureViewButton(); + m_layoutMDW->addWidget(m_configureViewButton, 1, 1, Qt::AlignRight|Qt::AlignBottom); + } - updateGuiOptions(); + updateGuiOptions(); } void ViewSliders::configurationUpdate() { - // Adjust height of top part by setting it to the maximum of all mdw's - bool haveCaptureLEDs = false; - int labelExtent = 0; - bool haveMuteButtons = false; + // Adjust the view layout by setting the extent of all the control labels + // to allow space for the largest. The extent of a control's label is + // found from its labelExtentHint() virtual function (which takes account + // of the control layout direction), and is then set by its setLabelExtent() + // virtual function. + + // The maximum extent is calculated and set separately for sliders and + // for switches (enums). + int labelExtentSliders = 0; + int labelExtentSwitches = 0; - // Find out whether any MDWSlider has Switches. If one has, then we need "extents" - for (int i = 0; i < _mdws.count(); i++) + const int num = mixDeviceCount(); + + // Pass 1: Set the visibility of all controls + for (int i = 0; i(_mdws[i]); - if (mdw && mdw->isVisibleTo(this)) - { - labelExtent = qMax(labelExtent, mdw->labelExtentHint()); - //qCDebug(KMIX_LOG) << "########## EXTENT for " << id() << " is " << labelExtent; - haveCaptureLEDs = haveCaptureLEDs || mdw->hasCaptureLED(); - haveMuteButtons = haveMuteButtons || mdw->hasMuteButton(); - } + MixDeviceWidget *mdw = qobject_cast(mixDeviceAt(i)); + if (mdw==nullptr) continue; + + // The GUI level has been set earlier, by inspecting the controls + ProfControl *matchingControl = findMdw(mdw->mixDevice()->id()); + mdw->setVisible(matchingControl!=nullptr); + } + + // Pass 2: Find the maximum extent of all applicable controls + for (int i = 0; iisVisibleTo(this)) continue; // not currently visible - // The following "break" cannot be done any longer, as we need to calculate the highest labelExtent from ALL mdw's - // if (haveCaptureLEDs && haveMuteButtons) - // break; // We know all we want. Lets break. + const MDWSlider *mdws = qobject_cast(w); + if (mdws!=nullptr) labelExtentSliders = qMax(labelExtentSliders, mdws->labelExtentHint()); + + const MDWEnum *mdwe = qobject_cast(w); + if (mdwe!=nullptr) labelExtentSwitches = qMax(labelExtentSwitches, mdwe->labelExtentHint()); } - //qCDebug(KMIX_LOG) << "topPartExtent is " << topPartExtent; - bool firstVisibleControlFound = false; - for ( int i=0; i<_mdws.count(); i++ ) - { - MixDeviceWidget* mdw = ::qobject_cast(_mdws[i]); - MDWSlider* mdwSlider = ::qobject_cast(_mdws[i]); - if ( mdw ) - { - // guiLevel has been set earlier, by inspecting the controls - ProfControl* matchingControl = findMdw(mdw->mixDevice()->id(), guiLevel); - mdw->setVisible(matchingControl != 0); - - if ( mdwSlider ) + + // Pass 3: Set the maximum extent of all applicable controls + for (int i = 0; isetLabelExtent(labelExtent); - mdwSlider->setMuteButtonSpace(haveMuteButtons); - mdwSlider->setCaptureLEDSpace(haveCaptureLEDs); + QWidget *w = mixDeviceAt(i); + Q_ASSERT(w!=nullptr); + if (!w->isVisibleTo(this)) continue; // not currently visible + + MDWSlider *mdws = qobject_cast(w); + if (mdws!=nullptr && labelExtentSliders>0) mdws->setLabelExtent(labelExtentSliders); + + MDWEnum *mdwe = qobject_cast(w); + if (mdwe!=nullptr && labelExtentSwitches>0) mdwe->setLabelExtent(labelExtentSwitches); } - bool thisControlIsVisible = mdw->isVisibleTo(this); - bool showSeparator = ( firstVisibleControlFound && thisControlIsVisible); - if ( _separators.contains( mdw->mixDevice()->id() )) - { - QFrame* sep = _separators[mdw->mixDevice()->id()]; - sep->setVisible(showSeparator); - } - if ( thisControlIsVisible ) - firstVisibleControlFound=true; - } - } // for all MDW's - - _layoutMDW->activate(); + + // An old comment here said that this was necessary for KDE3. + // Not sure if it is still required two generations later. + m_layoutMDW->activate(); } + void ViewSliders::refreshVolumeLevels() { - for (int i = 0; i < _mdws.count(); i++) + const int num = mixDeviceCount(); + for (int i = 0; i(mdwx); - if (mdw != 0) + MixDeviceWidget *mdw = qobject_cast(mixDeviceAt(i)); + if (mdw!=nullptr) { // sanity check #ifdef TEST_MIXDEVICE_COMPOSITE // --- start --- The following 4 code lines should be moved to a more // generic place, as it only works in this View. But it // should also work in the ViewDockareaPopup and everywhere else. MixDeviceComposite* mdc = ::qobject_cast(mdw->mixDevice()); if (mdc != 0) { mdc->update(); } // --- end --- #endif if (GlobalConfig::instance().data.debugVolume) { bool debugMe = (mdw->mixDevice()->id() == "PCM:0"); if (debugMe) qCDebug(KMIX_LOG) << "Old PCM:0 playback state" << mdw->mixDevice()->isMuted() << ", vol=" << mdw->mixDevice()->playbackVolume().getAvgVolumePercent(Volume::MALL); } mdw->update(); } else { qCCritical(KMIX_LOG) << "ViewSliders::refreshVolumeLevels(): mdw is not a MixDeviceWidget\n"; // no slider. Cannot happen in theory => skip it } } } + +Qt::Orientation ViewSliders::orientationSetting() const +{ + return (GlobalConfig::instance().data.getToplevelOrientation()); +} diff --git a/gui/viewsliders.h b/gui/viewsliders.h index 127d5d5e..456bd5b2 100644 --- a/gui/viewsliders.h +++ b/gui/viewsliders.h @@ -1,64 +1,66 @@ //-*-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 ViewSliders_h #define ViewSliders_h +#include "viewbase.h" + class QBoxLayout; -#include -#include -class QLabel; -#include -class QWidget; +class QGridLayout; +class QPushButton; +class KMessageWidget; class Mixer; + #include "gui/viewbase.h" #include "core/ControlManager.h" + class ViewSliders : public ViewBase { Q_OBJECT + public: - ViewSliders(QWidget* parent, QString id, Mixer* mixer, ViewBase::ViewFlags vflags, QString guiProfileId, KActionCollection *actColl); + ViewSliders(QWidget *parent, const QString &id, Mixer *mixer, ViewBase::ViewFlags vflags, const QString &guiProfileId, KActionCollection *actColl); virtual ~ViewSliders(); - QWidget* add(shared_ptr) Q_DECL_OVERRIDE; + QWidget *add(const shared_ptr md) Q_DECL_OVERRIDE; void constructionFinished() Q_DECL_OVERRIDE; void configurationUpdate() Q_DECL_OVERRIDE; public slots: void controlsChange(ControlManager::ChangeType changeType); protected: - void _setMixSet() Q_DECL_OVERRIDE; + void initLayout() Q_DECL_OVERRIDE; + Qt::Orientation orientationSetting() const Q_DECL_OVERRIDE; private: void refreshVolumeLevels() Q_DECL_OVERRIDE; - QBoxLayout* _layoutMDW; - QLayout* _layoutSliders; - QBoxLayout* _layoutEnum; - QHash _separators; - QPushButton* _configureViewButton; - QLabel* emptyStreamHint; + QGridLayout *m_layoutMDW; + QBoxLayout *m_layoutSliders; + QBoxLayout *m_layoutSwitches; + QPushButton *m_configureViewButton; + KMessageWidget *m_emptyStreamHint; }; #endif -