diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e451dc2..173c9b04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,357 +1,357 @@ 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 (KF5_MIN_VERSION "5.48.0") set (PA_MIN_VERSION "0.9.16") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMPackageConfigHelpers) include(ECMInstallIcons) include(ECMQtDeclareLoggingCategory) include(GenerateExportHeader) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core DBus Gui Widgets Xml ) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Config ConfigWidgets Crash DBusAddons DocTools GlobalAccel I18n IconThemes Notifications Solid WidgetsAddons WindowSystem XmlGui ) find_package(Plasma ${KF5_MIN_VERSION}) set_package_properties(Plasma PROPERTIES TYPE OPTIONAL PURPOSE "Required to build Plasma data engine" ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) # PulseAudio is an optional dependency # find_package(PulseAudio "${PA_MIN_VERSION}") # PulseAudio requires GLib2 if (PulseAudio_FOUND) find_package(GLIB2 REQUIRED) endif(PulseAudio_FOUND) # Canberra is an optional dependency find_package(Canberra) find_package(ALSA) include(CheckCXXSourceCompiles) check_cxx_source_compiles(" #include int main() { std::shared_ptr p; return 0; } " HAVE_STD_SHARED_PTR) check_cxx_source_compiles(" #include int main() { std::tr1::shared_ptr p; return 0; } " HAVE_STD_TR1_SHARED_PTR) configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h ) #################################################################################################### ########### compile definitions #################################################################### #################################################################################################### include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) # TODO: is the next line really needed? Nothing seems to use anything to do with tagging. if (MSVC) include_directories(${TAGLIB_INCLUDES}) endif (MSVC) if (ALSA_FOUND) set(HAVE_LIBASOUND2 TRUE) add_definitions(-DHAVE_LIBASOUND2) endif (ALSA_FOUND) if (PulseAudio_FOUND) add_definitions(-DHAVE_PULSE) include_directories(${PulseAudio_INCLUDE_DIRS}) include_directories(${GLIB2_INCLUDE_DIR}) endif (PulseAudio_FOUND) if (CANBERRA_FOUND) add_definitions(-DHAVE_CANBERRA) include_directories(${CANBERRA_INCLUDE_DIRS}) endif (CANBERRA_FOUND) #################################################################################################### ########### subdirectories ######################################################################### #################################################################################################### add_subdirectory(doc) add_subdirectory(pics) add_subdirectory(profiles) #add_subdirectory(tests) if (Plasma_FOUND) add_subdirectory(plasma) endif (Plasma_FOUND) #################################################################################################### ########### definitions: logging ################################################################### #################################################################################################### ecm_qt_declare_logging_category(kmix_debug_SRCS HEADER kmix_debug.h IDENTIFIER KMIX_LOG CATEGORY_NAME org.kde.kmix) #################################################################################################### ########### definitions: DBus adaptor ############################################################## #################################################################################################### set(kmix_adaptor_SRCS dbus/dbusmixerwrapper.cpp dbus/dbusmixsetwrapper.cpp dbus/dbuscontrolwrapper.cpp ) qt5_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.control.xml dbus/dbuscontrolwrapper.h DBusControlWrapper ) qt5_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.mixer.xml dbus/dbusmixerwrapper.h DBusMixerWrapper ) qt5_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.mixset.xml dbus/dbusmixsetwrapper.h DBusMixSetWrapper ) install(FILES dbus/org.kde.kmix.control.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) install(FILES dbus/org.kde.kmix.mixer.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) install(FILES dbus/org.kde.kmix.mixset.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) #################################################################################################### ########### definitions: backends ################################################################## #################################################################################################### set(kmix_backend_SRCS backends/mixer_backend.cpp backends/mixer_mpris2.cpp ) if (HAVE_LIBASOUND2) set(kmix_backend_SRCS ${kmix_backend_SRCS} backends/mixer_alsa9.cpp ) endif (HAVE_LIBASOUND2) if (PulseAudio_FOUND) set(kmix_backend_SRCS ${kmix_backend_SRCS} backends/mixer_pulse.cpp ) endif (PulseAudio_FOUND) #################################################################################################### ########### target: kmixcore library ############################################################### #################################################################################################### set(kmixcore_SRCS core/MediaController.cpp core/mixertoolbox.cpp core/kmixdevicemanager.cpp core/ControlManager.cpp core/GlobalConfig.cpp core/MasterControl.cpp core/mixer.cpp core/mixset.cpp core/mixdevice.cpp core/mixdevicecomposite.cpp core/volume.cpp ) add_library(kmixcore SHARED ${kmixcore_SRCS} ${kmix_adaptor_SRCS} ${kmix_backend_SRCS} ${kmix_debug_SRCS} ) target_link_libraries(kmixcore PUBLIC Qt5::Core Qt5::Widgets PRIVATE Qt5::DBus KF5::I18n KF5::Solid PUBLIC KF5::ConfigCore KF5::ConfigGui ) set_target_properties(kmixcore PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) generate_export_header(kmixcore BASE_NAME kmixcore EXPORT_FILE_NAME kmixcore_export.h) if (HAVE_LIBASOUND2) target_link_libraries(kmixcore PRIVATE ${ALSA_LIBRARIES}) endif (HAVE_LIBASOUND2) if (PulseAudio_FOUND) target_link_libraries(kmixcore PRIVATE ${PulseAudio_LIBRARIES} ${PulseAudio_MAINLOOP_LIBRARY} ${GLIB2_LIBRARIES}) endif (PulseAudio_FOUND) if (CANBERRA_FOUND) target_link_libraries(kmixcore PRIVATE ${CANBERRA_LIBRARIES}) endif (CANBERRA_FOUND) install(TARGETS kmixcore DESTINATION ${KDE_INSTALL_LIBDIR} LIBRARY NAMELINK_SKIP) #################################################################################################### ########### target: kmixgui library ################################################################ #################################################################################################### set(kmixgui_SRCS gui/dialogbase.cpp gui/dialogstatesaver.cpp gui/kmixdockwidget.cpp gui/kmixprefdlg.cpp gui/viewbase.cpp gui/viewdockareapopup.cpp gui/viewsliders.cpp gui/mixdevicewidget.cpp gui/mdwslider.cpp gui/mdwenum.cpp gui/kmixerwidget.cpp gui/ksmallslider.cpp gui/verticaltext.cpp gui/volumeslider.cpp gui/kmixtoolbox.cpp gui/dialogaddview.cpp gui/dialogviewconfiguration.cpp gui/dialogselectmaster.cpp gui/dialogchoosebackends.cpp gui/guiprofile.cpp gui/toggletoolbutton.cpp ) add_library(kmixgui STATIC ${kmixgui_SRCS} ${kmix_debug_SRCS} ) target_link_libraries(kmixgui kmixcore Qt5::Core Qt5::Widgets KF5::I18n KF5::ConfigCore KF5::ConfigGui KF5::IconThemes KF5::GlobalAccel KF5::Notifications KF5::XmlGui KF5::WindowSystem ) #################################################################################################### ########### target: kmix ########################################################################### #################################################################################################### set(kmix_SRCS apps/main.cpp apps/kmix.cpp apps/KMixApp.cpp ${kmix_debug_SRCS} ) add_executable(kmix ${kmix_SRCS}) target_link_libraries(kmix kmixcore kmixgui KF5::I18n KF5::IconThemes KF5::DBusAddons KF5::GlobalAccel KF5::XmlGui KF5::Notifications KF5::WindowSystem ) install(TARGETS kmix ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES desktop/kmixui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kmix) install(PROGRAMS desktop/org.kde.kmix.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES desktop/org.kde.kmix.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES desktop/kmix_autostart.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) install(FILES desktop/kmix.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR} ) #################################################################################################### ########### target: kded_kmixd ##################################################################### #################################################################################################### set(kmixd_SRCS apps/kmixd.cpp ${kmix_debug_SRCS} ) add_library(kded_kmixd MODULE ${kmixd_SRCS}) set_target_properties(kded_kmixd PROPERTIES OUTPUT_NAME kmixd) kcoreaddons_desktop_to_json(kded_kmixd desktop/kmixd.desktop) target_link_libraries(kded_kmixd kmixcore KF5::I18n KF5::CoreAddons KF5::DBusAddons ) install(TARGETS kded_kmixd DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kded) #################################################################################################### ########### target: kmixctrl ####################################################################### #################################################################################################### set(kmixctrl_SRCS apps/kmixctrl.cpp ${kmix_debug_SRCS} ) add_executable(kmixctrl ${kmixctrl_SRCS}) target_link_libraries(kmixctrl kmixcore KF5::I18n KF5::CoreAddons ) install(TARGETS kmixctrl ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES desktop/kmixctrl_restore.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) #################################################################################################### ########### other installs ######################################################################### #################################################################################################### install(PROGRAMS apps/kmixremote DESTINATION ${KDE_INSTALL_BINDIR}) install(FILES desktop/restore_kmix_volumes.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) #################################################################################################### ########### end #################################################################################### #################################################################################################### feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/apps/kmix.cpp b/apps/kmix.cpp index 94f78d1d..8b714684 100644 --- a/apps/kmix.cpp +++ b/apps/kmix.cpp @@ -1,1303 +1,1303 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 1996-2014 The KMix authors. Maintainer: Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "apps/kmix.h" // include files for Qt #include #include #include #include #include #include #include #include #include #include #include #include // include files for KDE #include #include #include #include #include #include #include #include #include // KMix #include "gui/guiprofile.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/MasterControl.h" #include "core/MediaController.h" #include "core/mixertoolbox.h" #include "core/kmixdevicemanager.h" #include "gui/kmixerwidget.h" #include "gui/kmixprefdlg.h" #include "gui/kmixdockwidget.h" #include "gui/kmixtoolbox.h" #include "core/version.h" #include "gui/viewdockareapopup.h" #include "gui/dialogaddview.h" #include "gui/dialogselectmaster.h" #include "dbus/dbusmixsetwrapper.h" #include "kmix_debug.h" #include #include #include /* KMixWindow * Constructs a mixer window (KMix main window) */ KMixWindow::KMixWindow(bool invisible, bool reset) : KXmlGuiWindow(0, Qt::WindowFlags( KDE_DEFAULT_WINDOWFLAGS | Qt::WindowContextHelpButtonHint)), m_multiDriverMode(false), // -<- I never-ever want the multi-drivermode to be activated by accident m_autouseMultimediaKeys(true), m_dockWidget(), m_dsm(0), m_dontSetDefaultCardOnStart(false) { setObjectName(QStringLiteral("KMixWindow")); // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in setAttribute(Qt::WA_DeleteOnClose, false); initActions(); // init actions first, so we can use them in the loadConfig() already loadAndInitConfig(reset); // Load config before initMixer(), e.g. due to "MultiDriver" keyword initActionsLate(); // init actions that require a loaded config // TODO: Port to KF5 //KGlobal::locale()->insertCatalog(QLatin1String("kmix-controls")); initWidgets(); initPrefDlg(); DBusMixSetWrapper::initialize(this, QStringLiteral("/Mixers")); MixerToolBox::initMixer(m_multiDriverMode, m_backendFilter, true); KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance(); initActionsAfterInitMixer(); // init actions that require initialized mixer backend(s). recreateGUI(false, reset); if (m_wsMixers->count() < 1) { // Something is wrong. Perhaps a hardware or driver or backend change. Let KMix search harder recreateGUI(false, QString(), true, reset); } if (!qApp->isSessionRestored() ) // done by the session manager otherwise setInitialSize(); fixConfigAfterRead(); connect(theKMixDeviceManager, &KMixDeviceManager::plugged, this, &KMixWindow::plugged); connect(theKMixDeviceManager, &KMixDeviceManager::unplugged, this, &KMixWindow::unplugged); theKMixDeviceManager->initHotplug(); if (m_startVisible && !invisible) show(); // Started visible connect(qApp, SIGNAL(aboutToQuit()), SLOT(saveConfig()) ); ControlManager::instance().addListener( QString(), // All mixers (as the Global master Mixer might change) ControlManager::ControlList|ControlManager::MasterChanged, this, "KMixWindow"); // Send an initial volume refresh (otherwise all volumes are 0 until the next change) ControlManager::instance().announce(QString(), ControlManager::Volume, "Startup"); } KMixWindow::~KMixWindow() { ControlManager::instance().removeListener(this); delete m_dsm; // -1- Cleanup Memory: clearMixerWidgets while (m_wsMixers->count() != 0) { QWidget *mw = m_wsMixers->widget(0); m_wsMixers->removeTab(0); delete mw; } // -2- Mixer HW MixerToolBox::deinitMixer(); // -3- Action collection (just to please Valgrind) actionCollection()->clear(); // GUIProfile cache should be cleared very very late, as GUIProfile instances are used in the Views, which // means main window and potentially also in the tray popup (at least we might do so in the future). // This place here could be to early, if we would start to GUIProfile outside KMixWIndow, e.g. in the tray popup. // Until we do so, this is the best place to call clearCache(). Later, e.g. in main() would likely be problematic. GUIProfile::clearCache(); } void KMixWindow::controlsChange(ControlManager::ChangeType changeType) { switch (changeType) { case ControlManager::ControlList: case ControlManager::MasterChanged: updateDocking(); break; default: ControlManager::warnUnexpectedChangeType(changeType, this); break; } } void KMixWindow::initActions() { // file menu KStandardAction::quit(this, SLOT(quit()), actionCollection()); // settings menu _actionShowMenubar = KStandardAction::showMenubar(this, SLOT(toggleMenuBar()), actionCollection()); //actionCollection()->addAction(QStringLiteral( a->objectName()), a ); KStandardAction::preferences(this, SLOT(showSettings()), actionCollection()); KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), actionCollection()); QAction* action = actionCollection()->addAction(QStringLiteral("launch_kdesoundsetup")); action->setText(i18n("Audio Setup...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotKdeAudioSetupExec())); action = actionCollection()->addAction(QStringLiteral("hide_kmixwindow")); action->setText(i18n("Hide Mixer Window")); connect(action, SIGNAL(triggered(bool)), SLOT(hideOrClose())); actionCollection()->setDefaultShortcut(action, Qt::Key_Escape); action = actionCollection()->addAction(QStringLiteral("toggle_channels_currentview")); action->setText(i18n("Configure &Channels...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotConfigureCurrentView())); action = actionCollection()->addAction(QStringLiteral("select_master")); action->setText(i18n("Select Master Channel...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotSelectMaster())); action = actionCollection()->addAction(QStringLiteral("save_1")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_1); action->setText(i18n("Save volume profile 1")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes1())); action = actionCollection()->addAction(QStringLiteral("save_2")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_2); action->setText(i18n("Save volume profile 2")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes2())); action = actionCollection()->addAction(QStringLiteral("save_3")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_3); action->setText(i18n("Save volume profile 3")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes3())); action = actionCollection()->addAction(QStringLiteral("save_4")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_4); action->setText(i18n("Save volume profile 4")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes4())); action = actionCollection()->addAction(QStringLiteral("load_1")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_1); action->setText(i18n("Load volume profile 1")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes1())); action = actionCollection()->addAction(QStringLiteral("load_2")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_2); action->setText(i18n("Load volume profile 2")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes2())); action = actionCollection()->addAction(QStringLiteral("load_3")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_3); action->setText(i18n("Load volume profile 3")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes3())); action = actionCollection()->addAction(QStringLiteral("load_4")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_4); action->setText(i18n("Load volume profile 4")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes4())); createGUI(QLatin1String("kmixui.rc")); } void KMixWindow::initActionsLate() { if (m_autouseMultimediaKeys) { QAction* globalAction = actionCollection()->addAction(QStringLiteral("increase_volume")); globalAction->setText(i18n("Increase Volume")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeUp); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotIncreaseVolume())); globalAction = actionCollection()->addAction(QStringLiteral("decrease_volume")); globalAction->setText(i18n("Decrease Volume")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeDown); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotDecreaseVolume())); globalAction = actionCollection()->addAction(QStringLiteral("mute")); globalAction->setText(i18n("Mute")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeMute); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotMute())); } } void KMixWindow::initActionsAfterInitMixer() { // Only show the new tab widget if Pulseaudio is not used. Hint: The Pulseaudio backend always // runs with 4 fixed Tabs. if (!Mixer::pulseaudioPresent()) { - QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new", KIconLoader::Toolbar, - IconSize(KIconLoader::Toolbar)); + QPixmap cornerNewPM = KIconLoader::global()->loadScaledIcon("tab-new", KIconLoader::Toolbar, + devicePixelRatioF(), 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; KMixerWidget *kmw = new KMixerWidget(mixer, this, vflags, guiprofId, actionCollection()); /* A newly added mixer will automatically added at the top * and thus the window title is also set appropriately */ /* * Skip the name from the profile for now. I would at least have to do the '&' quoting for the tab label. But I am * also not 100% sure whether the current name from the profile is any good - it does (likely) not even contain the * card ID. This means you cannot distinguish between cards with an identical name. */ // QString tabLabel = guiprof->getName(); // if (tabLabel.isEmpty()) // QString tabLabel = kmw->mixer()->readableName(true); QString tabLabel = kmw->mixer()->readableName(true); m_dontSetDefaultCardOnStart = true; // inhibit implicit setting of m_defaultCardOnStart if (insertPosition == -1) m_wsMixers->addTab(kmw, tabLabel); else m_wsMixers->insertTab(insertPosition, kmw, tabLabel); if (kmw->getGuiprof()->getId() == m_defaultCardOnStart) { m_wsMixers->setCurrentWidget(kmw); } updateTabsClosable(); m_dontSetDefaultCardOnStart = false; kmw->loadConfig(KSharedConfig::openConfig().data()); // Now force to read for new tabs, especially after hotplug. Note: Doing it here is bad design and possibly // obsolete, as the backend should take care of updating itself. kmw->mixer()->readSetFromHWforceUpdate(); return true; } void KMixWindow::updateTabsClosable() { // Pulseaudio runs with 4 fixed tabs - don't allow to close them. // Also do not allow to close the last view m_wsMixers->setTabsClosable(!Mixer::pulseaudioPresent() && m_wsMixers->count() > 1); } bool KMixWindow::queryClose() { GlobalConfigData& gcd = GlobalConfig::instance().data; if (gcd.showDockWidget && !qApp->isSavingSession() ) { // Hide (don't close and destroy), if docking is enabled. Except when session saving (shutdown) is in process. hide(); return false; } else { // Accept the close, if: // The user has disabled docking // or SessionSaving() is running // qCDebug(KMIX_LOG) << "close"; return true; } } void KMixWindow::hideOrClose() { GlobalConfigData& gcd = GlobalConfig::instance().data; if (gcd.showDockWidget && m_dockWidget != 0) { // we can hide if there is a dock widget hide(); } else { // if there is no dock widget, we will quit quit(); } } // internal helper to prevent code duplication in slotIncreaseVolume and slotDecreaseVolume void KMixWindow::increaseOrDecreaseVolume(bool increase) { Mixer* mixer = Mixer::getGlobalMasterMixer(); // only needed for the awkward construct below if (mixer == 0) return; // e.g. when no soundcard is available shared_ptr md = Mixer::getGlobalMasterMD(); if (md.get() == 0) return; // shouldn't happen, but lets play safe Volume::VolumeTypeFlag volumeType = md->playbackVolume().hasVolume() ? Volume::Playback : Volume::Capture; md->increaseOrDecreaseVolume(!increase, volumeType); md->mixer()->commitVolumeChange(md); showVolumeDisplay(); } void KMixWindow::slotIncreaseVolume() { increaseOrDecreaseVolume(true); } void KMixWindow::slotDecreaseVolume() { increaseOrDecreaseVolume(false); } void KMixWindow::showVolumeDisplay() { Mixer* mixer = Mixer::getGlobalMasterMixer(); if (mixer == 0) return; // e.g. when no soundcard is available shared_ptr md = Mixer::getGlobalMasterMD(); if (md.get() == 0) return; // shouldn't happen, but lets play safe if (GlobalConfig::instance().data.showOSD) { QDBusMessage msg = QDBusMessage::createMethodCall( "org.kde.plasmashell", "/org/kde/osdService", "org.kde.osdService", "volumeChanged" ); int currentVolume = 0; if (!md->isMuted()) { currentVolume = md->playbackVolume().getAvgVolumePercent(Volume::MALL); } msg.setArguments(QList() << currentVolume); QDBusConnection::sessionBus().asyncCall(msg); } } /** * Mutes the global master. (SLOT) */ void KMixWindow::slotMute() { Mixer* mixer = Mixer::getGlobalMasterMixer(); if (mixer == 0) return; // e.g. when no soundcard is available shared_ptr md = Mixer::getGlobalMasterMD(); if (md.get() == 0) return; // shouldn't happen, but lets play safe md->toggleMute(); mixer->commitVolumeChange(md); showVolumeDisplay(); } void KMixWindow::quit() { // qCDebug(KMIX_LOG) << "quit"; qApp->quit(); } /** * Shows the configuration dialog, with the "general" tab opened. */ void KMixWindow::showSettings() { KMixPrefDlg::getInstance()->switchToPage(KMixPrefDlg::PrefGeneral); KMixPrefDlg::getInstance()->show(); } void KMixWindow::showHelp() { actionCollection()->action("help_contents")->trigger(); } void KMixWindow::showAbout() { actionCollection()->action("help_about_app")->trigger(); } /** * Apply the Preferences from the preferences dialog. Depending on what has been changed, * the corresponding announcements are made. */ void KMixWindow::applyPrefs() { // -1- Determine what has changed ------------------------------------------------------------------ GlobalConfigData& config = GlobalConfig::instance().data; GlobalConfigData& configBefore = configDataSnapshot; bool labelsHasChanged = config.showLabels ^ configBefore.showLabels; bool ticksHasChanged = config.showTicks ^ configBefore.showTicks; bool dockwidgetHasChanged = config.showDockWidget ^ configBefore.showDockWidget; bool toplevelOrientationHasChanged = config.getToplevelOrientation() != configBefore.getToplevelOrientation(); bool traypopupOrientationHasChanged = config.getTraypopupOrientation() != configBefore.getTraypopupOrientation(); qCDebug(KMIX_LOG) << "toplevelOrientationHasChanged=" << toplevelOrientationHasChanged << ", config=" << config.getToplevelOrientation() << ", configBefore=" << configBefore.getToplevelOrientation(); qCDebug(KMIX_LOG) << "trayOrientationHasChanged=" << traypopupOrientationHasChanged << ", config=" << config.getTraypopupOrientation() << ", configBefore=" << configBefore.getTraypopupOrientation(); // -2- Determine what effect the changes have ------------------------------------------------------------------ if (dockwidgetHasChanged || toplevelOrientationHasChanged || traypopupOrientationHasChanged) { // These might need a complete relayout => announce a ControlList change to rebuild everything ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("Preferences Dialog")); } else if (labelsHasChanged || ticksHasChanged) { ControlManager::instance().announce(QString(), ControlManager::GUI, QString("Preferences Dialog")); } // showOSD does not require any information. It reads on-the-fly from GlobalConfig. // -3- Apply all changes ------------------------------------------------------------------ // this->repaint(); // make KMix look fast (saveConfig() often uses several seconds) qApp->processEvents(); configDataSnapshot = GlobalConfig::instance().data; // create a new snapshot as all current changes are applied now // Remove saveConfig() IF aa changes have been migrated to GlobalConfig. // Currently there is still stuff like "show menu bar". saveConfig(); } void KMixWindow::toggleMenuBar() { menuBar()->setVisible(_actionShowMenubar->isChecked()); } void KMixWindow::slotKdeAudioSetupExec() { forkExec(QStringList() << "kcmshell5" << "kcm_phonon"); } void KMixWindow::forkExec(const QStringList& args) { int pid = KProcess::startDetached(args); if (pid == 0) { KMessageBox::error(this, i18n("The helper application is either not installed or not working.\n\n%1", args.join(QLatin1String(" ")))); } } void KMixWindow::slotConfigureCurrentView() { KMixerWidget *mw = qobject_cast(m_wsMixers->currentWidget()); ViewBase* view = 0; if (mw) view = mw->currentView(); if (view) view->configureView(); } void KMixWindow::slotSelectMasterClose(QObject*) { m_dsm = 0; } void KMixWindow::slotSelectMaster() { Mixer *mixer = Mixer::getGlobalMasterMixer(); if (mixer != 0) { if (!m_dsm) { m_dsm = new DialogSelectMaster(Mixer::getGlobalMasterMixer(), this); connect(m_dsm, SIGNAL(destroyed(QObject*)), this, SLOT(slotSelectMasterClose(QObject*))); m_dsm->setAttribute(Qt::WA_DeleteOnClose, true); m_dsm->show(); } m_dsm->raise(); m_dsm->activateWindow(); } else { KMessageBox::error(0, i18n("No sound card is installed or currently plugged in.")); } } void KMixWindow::newMixerShown(int /*tabIndex*/) { KMixerWidget *kmw = qobject_cast(m_wsMixers->currentWidget()); if (kmw!=nullptr) { // I am using the app name as a PREFIX, as KMix is a single window app, and it is // more helpful to the user to see "KDE Mixer" in a window list than a possibly cryptic // soundcard name like "HDA ATI SB". // Reformatted for KF5 so as to not say "KDE" // and so that there are not two different dashes. setWindowTitle(i18n("Mixer (%1)", kmw->mixer()->readableName())); if (!m_dontSetDefaultCardOnStart) m_defaultCardOnStart = kmw->getGuiprof()->getId(); // As switching the tab does NOT mean switching the master card, we do not need to update dock icon here. // It would lead to unnecesary flickering of the (complete) dock area. // We only show the "Configure Channels..." menu item if the mixer is not dynamic ViewBase* view = kmw->currentView(); QAction* action = actionCollection()->action("toggle_channels_currentview"); if (view && action) action->setVisible(!view->isDynamic()); } } diff --git a/apps/main.cpp b/apps/main.cpp index a84d0146..6b002884 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -1,86 +1,88 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2000 Stefan Schimanski * * 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 #include #include #include #include #include "KMixApp.h" #include "core/version.h" #include "kmix_debug.h" static const char description[] = I18N_NOOP("KMix - KDE's full featured mini mixer"); int main(int argc, char *argv[]) { + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + QApplication qapp(argc, argv); KLocalizedString::setApplicationDomain("kmix"); KAboutData aboutData("kmix", i18n("KMix"), APP_VERSION, i18n(description), KAboutLicense::GPL, i18n("(c) 1996-2013 The KMix Authors")); // Author Policy: Long-term maintainers and backend writers/maintainers go in the Authors list. aboutData.addAuthor(i18n("Christian Esken") , i18n("Original author and current maintainer"), "esken@kde.org"); aboutData.addAuthor(i18n("Colin Guthrie") , i18n("PulseAudio support"), "colin@mageia.org"); aboutData.addAuthor(i18n("Helio Chissini de Castro"), i18n("ALSA 0.9x port"), "helio@kde.org" ); aboutData.addAuthor(i18n("Brian Hanson") , i18n("Solaris support"), "bhanson@hotmail.com"); // The HP/UX port is not maintained anymore, and no official part of KMix anymore // aboutData.addAuthor(i18n("Helge Deller") , i18n("HP/UX port"), "deller@gmx.de"); // The initial support was for ALSA 0.5. The new code is not based on it IIRC. // aboutData.addAuthor(i18n("Nick Lopez") , i18n("Initial ALSA port"), "kimo_sabe@usa.net"); // Credit Policy: Authors who did a discrete part, like the Dataengine, OSD, help on specific platforms or soundcards. aboutData.addCredit(i18n("Igor Poboiko") , i18n("Plasma Dataengine"), "igor.poboiko@gmail.com"); aboutData.addCredit(i18n("Stefan Schimanski") , i18n("Temporary maintainer"), "schimmi@kde.org"); aboutData.addCredit(i18n("Sebestyen Zoltan") , i18n("*BSD fixes"), "szoli@digo.inf.elte.hu"); aboutData.addCredit(i18n("Lennart Augustsson"), i18n("*BSD fixes"), "augustss@cs.chalmers.se"); aboutData.addCredit(i18n("Nadeem Hasan") , i18n("Mute and volume preview, other fixes"), "nhasan@kde.org"); aboutData.addCredit(i18n("Erwin Mascher") , i18n("Improving support for emu10k1 based soundcards")); aboutData.addCredit(i18n("Valentin Rusu") , i18n("TerraTec DMX6Fire support"), "kde@rusu.info"); aboutData.setOrganizationDomain(QByteArray("kde.org")); aboutData.setDesktopFileName(QStringLiteral("org.kde.kmix")); KAboutData::setApplicationData(aboutData); // Implement running as a unique application KDBusService service(KDBusService::Unique); KMixApp kmapp; // continues here if unique QObject::connect(&service, &KDBusService::activateRequested, &kmapp, &KMixApp::newInstance); // Now known to be the first or only instance of the application QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.addOption(QCommandLineOption("keepvisibility", i18n("Inhibits the unhiding of the KMix main window, if KMix is already running."))); parser.addOption(QCommandLineOption("failsafe", i18n("Starts KMix in failsafe mode."))); parser.process(qapp); kmapp.parseOptions(parser); // pass options for startup kmapp.newInstance(); int ret = qapp.exec(); qCDebug(KMIX_LOG) << "KMix is now exiting, status=" << ret; return ret; } diff --git a/gui/dialogselectmaster.cpp b/gui/dialogselectmaster.cpp index 5c7db5ca..02640deb 100644 --- a/gui/dialogselectmaster.cpp +++ b/gui/dialogselectmaster.cpp @@ -1,232 +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; i md = mixer->getLocalMasterMD(); const QString iconName = (md!=nullptr) ? md->iconName() : "media-playback-start"; m_cMixer->addItem(QIcon::fromTheme(iconName), mixer->readableName(), mixer->id() ); } // end for all_Mixers // Make the current Mixer the current item in the ComboBox int findIndex = m_cMixer->findData( ptr_mixer->id() ); if ( findIndex != -1 ) m_cMixer->setCurrentIndex( findIndex ); m_cMixer->setToolTip( i18n("Current mixer" ) ); mixerNameLayout->addWidget(m_cMixer, 1); layout->addSpacing(DialogBase::verticalSpacing()); } // end if (more_than_1_Mixer) if ( Mixer::mixers().count() > 0 ) { QLabel *qlbl = new QLabel( i18n("Select the channel representing the master volume:"), mainFrame ); layout->addWidget(qlbl); createPage(ptr_mixer); connect(this, SIGNAL(accepted()), this, SLOT(apply())); } else { QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), mainFrame ); layout->addWidget(qlbl); } } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogSelectMaster::createPageByID(int mixerId) { QString mixer_id = m_cMixer->itemData(mixerId).toString(); Mixer * mixer = Mixer::findMixer(mixer_id); if ( mixer != NULL ) createPage(mixer); } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogSelectMaster::createPage(Mixer* mixer) { /** --- Reset page ----------------------------------------------- * In case the user selected a new Mixer via m_cMixer, we need * to remove the stuff created on the last call. */ // delete the list widget. // This should automatically remove all contained items. delete m_channelSelector; /** Reset page end -------------------------------------------------- */ QWidget *mainFrame = mainWidget(); QVBoxLayout *layout = qobject_cast(mainFrame->layout()); Q_ASSERT(layout!=nullptr); m_channelSelector = new QListWidget(mainFrame); #ifndef QT_NO_ACCESSIBILITY m_channelSelector->setAccessibleName( i18n("Select Master Channel") ); #endif m_channelSelector->setSelectionMode(QAbstractItemView::SingleSelection); m_channelSelector->setDragEnabled(false); m_channelSelector->setAlternatingRowColors(true); layout->addWidget(m_channelSelector); // shared_ptr master = mixer->getLocalMasterMD(); // QString masterKey = ( master.get() != 0 ) ? master->id() : "----noMaster---"; // Use non-matching name as default const MixSet& mixset = mixer->getMixSet(); MixSet& mset = const_cast(mixset); MasterControl mc = mixer->getGlobalMasterPreferred(false); QString masterKey = mc.getControl(); if (!masterKey.isEmpty() && !mset.get(masterKey)) { shared_ptr master = mixer->getLocalMasterMD(); if (master.get() != 0) masterKey = master->id(); } int msetCount = 0; for (int i = 0; i < mset.count(); ++i) { shared_ptr md = mset[i]; if ( md->playbackVolume().hasVolume() ) ++msetCount; } if (msetCount > 0 && !mixer->isDynamic()) { QString mdName = i18n("Automatic (%1 recommendation)", mixer->getDriverName()); - QPixmap icon = KIconLoader::global()->loadIcon("audio-volume-high", KIconLoader::Small, IconSize(KIconLoader::Small)); + QPixmap icon = KIconLoader::global()->loadScaledIcon("audio-volume-high", KIconLoader::Small, devicePixelRatioF(), 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)); + QPixmap icon = KIconLoader::global()->loadScaledIcon(md->iconName(), KIconLoader::Small, devicePixelRatioF(), 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 2f1a3433..f8222244 100644 --- a/gui/dialogviewconfiguration.cpp +++ b/gui/dialogviewconfiguration.cpp @@ -1,373 +1,374 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dialogviewconfiguration.h" #include #include #include #include #include #include #include #include "gui/guiprofile.h" #include "gui/mixdevicewidget.h" #include "core/ControlManager.h" #include "core/mixdevice.h" #include "core/mixer.h" /** * Standard constructor. */ -DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, const QString &id, bool shown, const QString &name, int splitted, const QString& iconName) : - QListWidgetItem(parent), _id(id), _shown(shown), _name(name), _splitted(splitted), _iconName(iconName) +DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, qreal devicePixelRatio, const QString &id, bool shown, const QString &name, int splitted, const QString& iconName) : + QListWidgetItem(parent), _id(id), _shown(shown), _name(name), _splitted(splitted), _iconName(iconName), _devicePixelRatio(devicePixelRatio) { refreshItem(); } /** * Deserializing constructor. Used for DnD. */ -DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, QDataStream &s) +DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, qreal devicePixelRatio, QDataStream &s) : QListWidgetItem(parent) + , _devicePixelRatio(devicePixelRatio) { s >> _id; s >> _shown; s >> _name; s >> _splitted; s >> _iconName; refreshItem(); } void DialogViewConfigurationItem::refreshItem() { setFlags((flags() | Qt::ItemIsDragEnabled) & ~Qt::ItemIsDropEnabled); setText(_name); - setIcon(KIconLoader::global()->loadIcon( _iconName, KIconLoader::Small, IconSize(KIconLoader::Toolbar) )); + setIcon(KIconLoader::global()->loadScaledIcon( _iconName, KIconLoader::Small, _devicePixelRatio, 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. */ QDataStream &DialogViewConfigurationItem::serialize(QDataStream &s) const { s << _id; s << _shown; s << _name; s << _splitted; s << _iconName; return (s); } DialogViewConfigurationWidget::DialogViewConfigurationWidget(QWidget *parent) : QListWidget(parent), m_activeList(true) { setDragDropMode(QAbstractItemView::DragDrop); setDropIndicatorShown(true); setAcceptDrops(true); setSelectionMode(QAbstractItemView::SingleSelection); setDragEnabled(true); viewport()->setAcceptDrops(true); setAlternatingRowColors(true); } QMimeData* DialogViewConfigurationWidget::mimeData(const QList items) const { if (items.isEmpty()) return 0; QMimeData* mimedata = new QMimeData(); QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); // we only support single selection DialogViewConfigurationItem* item = static_cast(items.first()); stream << *item; bool active = isActiveList(); mimedata->setData("application/x-kde-action-list", data); mimedata->setData("application/x-kde-source-treewidget", active ? "active" : "inactive"); return mimedata; } bool DialogViewConfigurationWidget::dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction /*action*/) { const QByteArray data = mimeData->data("application/x-kde-action-list"); if (data.isEmpty()) return false; QDataStream stream(data); - DialogViewConfigurationItem* item = new DialogViewConfigurationItem(nullptr, stream); + DialogViewConfigurationItem* item = new DialogViewConfigurationItem(nullptr, devicePixelRatioF(), stream); emit dropped(this, index, item); return true; } DialogViewConfiguration::DialogViewConfiguration(QWidget *parent, ViewBase &view) : DialogBase(parent), _view(view) { setWindowTitle( i18n( "Configure Channels" ) ); setButtons( QDialogButtonBox::Ok|QDialogButtonBox::Cancel ); QWidget *frame = new QWidget( this ); frame->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); setMainWidget( frame ); // The _layout will hold two items: The title and the Drag-n-Drop area QVBoxLayout *layout = new QVBoxLayout(frame); // --- HEADER --- QLabel *qlb = new QLabel( i18n("Configure the visible channels. Drag icons between the lists to update."), frame ); layout->addWidget(qlb); _glayout = new QGridLayout(); _glayout->setMargin(0); layout->addLayout(_glayout); createPage(); } /** * Drop an item from one list to the other. */ void DialogViewConfiguration::slotDropped(DialogViewConfigurationWidget *list, int index, DialogViewConfigurationItem *item) { //qCDebug(KMIX_LOG) << "dropped item (index" << index << "): " << item->_id << item->_shown << item->_name << item->_splitted << item->_iconName; list->insertItem(index, item); } void DialogViewConfiguration::addSpacer(int row, int col) { QWidget *dummy = new QWidget(); dummy->setFixedWidth(4); _glayout->addWidget(dummy,row,col); } void DialogViewConfiguration::moveSelection(DialogViewConfigurationWidget *from, DialogViewConfigurationWidget *to) { const QList sel = from->selectedItems(); from->selectionModel()->clearSelection(); foreach (QListWidgetItem *item, sel) { from->takeItem(from->row(item)); to->addItem(item); to->setCurrentItem(item); } } void DialogViewConfiguration::moveSelectionToActiveList() { moveSelection(_qlwInactive, _qlwActive); } void DialogViewConfiguration::moveSelectionToInactiveList() { moveSelection(_qlwActive, _qlwInactive); } void DialogViewConfiguration::selectionChangedActive() { moveRightButton->setEnabled(!_qlwActive->selectedItems().isEmpty()); moveLeftButton->setEnabled(false); } void DialogViewConfiguration::selectionChangedInactive() { moveLeftButton->setEnabled(!_qlwInactive->selectedItems().isEmpty()); moveRightButton->setEnabled(false); } /** * Create basic widgets of the Dialog. */ void DialogViewConfiguration::createPage() { QLabel *l1 = new QLabel( i18n("Visible channels:") ); _glayout->addWidget(l1,0,0); QLabel *l2 = new QLabel( i18n("Available channels:") ); _glayout->addWidget(l2,0,6); QWidget *frame = mainWidget(); _qlwInactive = new DialogViewConfigurationWidget(frame); _qlwInactive->setDragDropMode(QAbstractItemView::DragDrop); _qlwInactive->setActiveList(false); _glayout->addWidget(_qlwInactive,1,6); connect(_qlwInactive, &DialogViewConfigurationWidget::dropped, this, &DialogViewConfiguration::slotDropped); addSpacer(1,1); const QIcon& icon = QIcon::fromTheme( QLatin1String( "arrow-left" )); moveLeftButton = new QPushButton(icon, ""); moveLeftButton->setEnabled(false); moveLeftButton->setToolTip(i18n("Move the selected channel to the visible list")); _glayout->addWidget(moveLeftButton,1,2); connect(moveLeftButton, &QPushButton::clicked, this, &DialogViewConfiguration::moveSelectionToActiveList); addSpacer(1,3); const QIcon& icon2 = QIcon::fromTheme( QLatin1String( "arrow-right" )); moveRightButton = new QPushButton(icon2, ""); moveRightButton->setEnabled(false); moveRightButton->setToolTip(i18n("Move the selected channel to the available (hidden) list")); _glayout->addWidget(moveRightButton,1,4); connect(moveRightButton, &QPushButton::clicked, this, &DialogViewConfiguration::moveSelectionToInactiveList); addSpacer(1,5); _qlwActive = new DialogViewConfigurationWidget(frame); _glayout->addWidget(_qlwActive,1,0); connect(_qlwActive, &DialogViewConfigurationWidget::dropped, this, &DialogViewConfiguration::slotDropped); // --- CONTROLS IN THE GRID ------------------------------------ //QPalette::ColorRole bgRole; const int num = _view.mixDeviceCount(); for (int i = 0; i(_view.mixDeviceAt(i)); if (mdw==nullptr) continue; //if ( i%2 == 0) bgRole = QPalette::Base; else bgRole = QPalette::AlternateBase; const shared_ptr md = mdw->mixDevice(); const QString mdName = md->readableName(); int splitted = -1; if ( ! md->isEnum() ) { splitted = ( md->playbackVolume().count() > 1) || ( md->captureVolume().count() > 1 ) ; } //qCDebug(KMIX_LOG) << "add DialogViewConfigurationItem: " << mdName << " visible=" << mdw->isVisible() << "splitted=" << splitted; - auto *item = new DialogViewConfigurationItem(nullptr, md->id(), true, mdName, splitted, mdw->mixDevice()->iconName()); + auto *item = new DialogViewConfigurationItem(nullptr, devicePixelRatioF(), md->id(), true, mdName, splitted, mdw->mixDevice()->iconName()); if (mdw->isVisible()) _qlwActive->addItem(item); else _qlwInactive->addItem(item); } // for all MDW's connect(_qlwInactive, &QListWidget::itemSelectionChanged, this, &DialogViewConfiguration::selectionChangedInactive); connect(_qlwActive, &QListWidget::itemSelectionChanged, this, &DialogViewConfiguration::selectionChangedActive); updateGeometry(); connect(this, &QDialog::accepted, this, &DialogViewConfiguration::apply); #ifndef QT_NO_ACCESSIBILITY moveLeftButton->setAccessibleName( i18n("Show the selected channel") ); moveRightButton->setAccessibleName( i18n("Hide the selected channel") ); _qlwActive->setAccessibleName( i18n("Visible channels") ); _qlwInactive->setAccessibleName( i18n("Available channels") ); #endif } void DialogViewConfiguration::apply() { // --- We have a 3-Step Apply of the Changes ------------------------------- // -1- Update view and profile ***************************************** GUIProfile* prof = _view.guiProfile(); const GUIProfile::ControlSet &oldControlset = prof->getControls(); GUIProfile::ControlSet newControlset; QAbstractItemModel* model; model = _qlwActive->model(); prepareControls(model, true, oldControlset, newControlset); model = _qlwInactive->model(); prepareControls(model, false, oldControlset, newControlset); // -2- Copy all mandatory "catch-all" controls form the old to the new ControlSet ******* foreach ( ProfControl* pctl, oldControlset) { if ( pctl->isMandatory() ) { ProfControl* newCtl = new ProfControl(*pctl); // The user has selected controls => mandatory controls (RegExp templates) should not been shown any longer newCtl->setVisibility(GuiVisibility::Never); newControlset.push_back(newCtl); } } prof->setControls(newControlset); prof->finalizeProfile(); prof->setDirty(); // --- Step 3: Tell the view, that it has changed (probably it needs some "polishing" --- if ( _view.getMixers().size() == 1 ) ControlManager::instance().announce(_view.getMixers().first()->id(), ControlManager::ControlList, QString("View Configuration Dialog")); else ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("View Configuration Dialog")); } void DialogViewConfiguration::prepareControls(QAbstractItemModel* model, bool isActiveView, const GUIProfile::ControlSet &oldCtlSet, GUIProfile::ControlSet &newCtlSet) { const int numRows = model->rowCount(); const int num = _view.mixDeviceCount(); for (int row = 0; row < numRows; ++row) { // -1- Extract the value from the model *************************** QModelIndex index = model->index(row, 0); QVariant vdci; vdci = model->data(index, Qt::ToolTipRole); // TooltipRole stores the ID (well, thats not really clean design, but it works) QString ctlId = vdci.toString(); // -2- Find the mdw, und update it ************************** for (int i = 0; i(_view.mixDeviceAt(i)); if (mdw==nullptr) continue; if ( mdw->mixDevice()->id() == ctlId ) { mdw->setVisible(isActiveView); break; } // mdw was found } // find mdw // -3- Insert it in the new ControlSet ************************** // qCDebug(KMIX_LOG) << "Should add to new ControlSet: " << ctlId; foreach ( ProfControl* control, oldCtlSet) { //qCDebug(KMIX_LOG) << " checking " << control->id; QRegExp idRegexp(control->id()); if ( ctlId.contains(idRegexp) ) { // found. Create a copy ProfControl* newCtl = new ProfControl(*control); newCtl->setId('^' + ctlId + '$'); // Replace the (possible generic) regexp by the actual ID // We have made this an an actual control. As it is derived (from e.g. ".*") it is NOT mandatory. newCtl->setMandatory(false); newCtl->setVisible(isActiveView); newCtlSet.push_back(newCtl); // qCDebug(KMIX_LOG) << "Added to new ControlSet (done): " << newCtl->id; break; } } } } diff --git a/gui/dialogviewconfiguration.h b/gui/dialogviewconfiguration.h index fb8329b3..d4094e53 100644 --- a/gui/dialogviewconfiguration.h +++ b/gui/dialogviewconfiguration.h @@ -1,144 +1,145 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DIALOGVIEWCONFIGURATION_H #define DIALOGVIEWCONFIGURATION_H // Qt #include class QLabel; #include class QHBoxLayout; #include #include class QScrollArea; class QVBoxLayout; // Qt DND #include #include // KMix #include "dialogbase.h" #include "gui/guiprofile.h" #include "viewbase.h" class DialogViewConfigurationItem : public QListWidgetItem { public: - DialogViewConfigurationItem(QListWidget *parent, QDataStream &s); - DialogViewConfigurationItem(QListWidget *parent, const QString &id, bool shown, const QString &name, int splitted, const QString &iconName); + DialogViewConfigurationItem(QListWidget *parent, qreal devicePixelRatio, QDataStream &s); + DialogViewConfigurationItem(QListWidget *parent, qreal devicePixelRatio, const QString &id, bool shown, const QString &name, int splitted, const QString &iconName); ~DialogViewConfigurationItem() = default; private: void refreshItem(); QDataStream &serialize(QDataStream &s) const; friend QDataStream &operator<<(QDataStream &s, const DialogViewConfigurationItem &item) { return (item.serialize(s)); } private: // TODO: are '_shown', '_splitted' and '_iconName' actually used? QString _id; bool _shown; QString _name; int _splitted; QString _iconName; + qreal _devicePixelRatio; }; class DialogViewConfigurationWidget : public QListWidget { Q_OBJECT public: explicit DialogViewConfigurationWidget(QWidget *parent=0); void setActiveList(bool isActiveList) { m_activeList = isActiveList; } bool isActiveList() const { return m_activeList; }; Q_SIGNALS: void dropped(DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item); protected: QMimeData* mimeData(const QList items) const Q_DECL_OVERRIDE; bool dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction action) Q_DECL_OVERRIDE; Qt::DropActions supportedDropActions() const Q_DECL_OVERRIDE { //qCDebug(KMIX_LOG) << "supportedDropActions!"; return Qt::MoveAction; } QStringList mimeTypes() const Q_DECL_OVERRIDE { //qCDebug(KMIX_LOG) << "mimeTypes!"; return QStringList() << "application/x-kde-action-list"; } // Skip internal dnd handling in QListWidget ---- how is one supposed to figure this out // without reading the QListWidget code !? void dropEvent(QDropEvent* ev) Q_DECL_OVERRIDE { QAbstractItemView::dropEvent(ev); } private: bool m_activeList; }; class DialogViewConfiguration : public DialogBase { Q_OBJECT public: DialogViewConfiguration(QWidget* parent, ViewBase& view); virtual ~DialogViewConfiguration() = default; public slots: void apply(); private slots: void slotDropped(DialogViewConfigurationWidget *list, int index, DialogViewConfigurationItem *item); void moveSelectionToActiveList(); void moveSelectionToInactiveList(); void selectionChangedActive(); void selectionChangedInactive(); private: //void dragEnterEvent(QDragEnterEvent *event); void prepareControls(QAbstractItemModel* model, bool isActiveView, const GUIProfile::ControlSet &oldCtlSet, GUIProfile::ControlSet &newCtlSet); void createPage(); void addSpacer(int row, int col); void moveSelection(DialogViewConfigurationWidget* from, DialogViewConfigurationWidget* to); ViewBase& _view; QGridLayout *_glayout; QPushButton* moveLeftButton; QPushButton* moveRightButton; DialogViewConfigurationWidget *_qlwActive; DialogViewConfigurationWidget *_qlwInactive; }; #endif diff --git a/gui/toggletoolbutton.cpp b/gui/toggletoolbutton.cpp index 79824cd9..55bbaa45 100644 --- a/gui/toggletoolbutton.cpp +++ b/gui/toggletoolbutton.cpp @@ -1,155 +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 getPixmap(const QString &name, qreal devicePixelRatio, bool small) { - QPixmap pix = KIconLoader::global()->loadIcon(name, iconLoadGroup, IconSize(iconSizeGroup)); + QPixmap pix = KIconLoader::global()->loadScaledIcon(name, iconLoadGroup, devicePixelRatio, IconSize(iconSizeGroup)); if (!pix.isNull()) // load icon, check success { // if wanting small size, scale pixmap - if (small) pix = pix.scaled(iconSmallSize, iconSmallSize); + if (small) pix = pix.scaled(QSize(iconSmallSize, iconSmallSize) * devicePixelRatio); } else qCWarning(KMIX_LOG) << "failed to load" << name; // Return the allocated pixmap even if it failed to load, so that // the caller can tell and only one load attempt will be made. return (pix); } void ToggleToolButton::setActive(bool active) { if (!mFirstTime && (active==mIsActive)) return; // no change required mIsActive = active; // record required state mFirstTime = false; // note now initialised QPixmap *pix = nullptr; // new pixmap to set 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); + if (!mActiveLoaded) mActivePixmap = getPixmap(mActiveIcon, devicePixelRatioF(), 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); + if (!mInactiveLoaded) mInactivePixmap = getPixmap(mInactiveIcon, devicePixelRatioF(), mSmallSize); } else { // need to derive from active state - if (!mActiveLoaded) mActivePixmap = getPixmap(mActiveIcon, mSmallSize); + if (!mActiveLoaded) mActivePixmap = getPixmap(mActiveIcon, devicePixelRatioF(), 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); + QPixmap pix = getPixmap(iconName, label->devicePixelRatioF(), 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 } }