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/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/gui/mdwenum.cpp b/gui/mdwenum.cpp index 5bc9f4a5..d08b35f6 100644 --- a/gui/mdwenum.cpp +++ b/gui/mdwenum.cpp @@ -1,161 +1,191 @@ /* * 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 "viewbase.h" #include "core/mixer.h" // KDE #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, QWidget* parent, ViewBase* view, ProfControl* par_pctl) : MixDeviceWidget(md, false, parent, view, par_pctl), - _label(0), _enumCombo(0), _layout(0) + _label(0), _enumCombo(0) { // 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(); } 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); - } + 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( m_mixdevice->readableName(), this); _layout->addWidget(_label); + + if (orientation()==Qt::Horizontal) _layout->addSpacing(8); + _enumCombo = new QComboBox(this); _enumCombo->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); // ------------ fill ComboBox start ------------ int maxEnumId= m_mixdevice->enumValues().count(); for (int i=0; iaddItem( m_mixdevice->enumValues().at(i)); } // ------------ fill ComboBox end -------------- _layout->addWidget(_enumCombo); connect( _enumCombo, SIGNAL(activated(int)), this, SLOT(setEnumId(int)) ); _enumCombo->setToolTip( m_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() ); } else { qCCritical(KMIX_LOG) << "MDWEnum::update() enumID=" << m_mixdevice->enumId() << " is no Enum ... skipped"; } } void MDWEnum::showContextMenu(const QPoint& pos ) { if( m_view == 0 ) return; QMenu *menu = m_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() ) { int curEnum = enumId(); if ( curEnum < m_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 ); } } int MDWEnum::enumId() { if ( m_mixdevice->isEnum() ) { return m_mixdevice->enumId(); } else { return 0; } } void MDWEnum::setDisabled( bool hide ) { emit guiVisibilityChange(this, !hide); } + +/** + * 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). + */ +int MDWEnum::labelExtentHint() const +{ + 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 c97f47ff..e67d124e 100644 --- a/gui/mdwenum.h +++ b/gui/mdwenum.h @@ -1,69 +1,69 @@ //-*-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 // 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, QWidget* parent, ViewBase* view, ProfControl* pctl); virtual ~MDWEnum() = default; void addActionToPopup( QAction *action ); QSizePolicy sizePolicy() const; + 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.h b/gui/mdwslider.h index ea0135bb..00b3eb74 100644 --- a/gui/mdwslider.h +++ b/gui/mdwslider.h @@ -1,163 +1,163 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * * Copyright Chrisitan Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MDWSLIDER_H #define MDWSLIDER_H #include #include #include "gui/volumeslider.h" #include "gui/mixdevicewidget.h" #include "core/volume.h" class QBoxLayout; class QGridLayout; class QToolButton; class QLabel; class QMenu; class MixDevice; class VerticalText; class ViewBase; class ToggleToolButton; class MDWSlider : public MixDeviceWidget { Q_OBJECT public: MDWSlider( shared_ptr md, bool includePlayback, bool includeCapture, bool includeMixerName, bool small, QWidget* parent, ViewBase* view, ProfControl *pctl); virtual ~MDWSlider(); enum LabelType { LT_ALL, LT_FIRST_CAPTURE, LT_NONE }; void addActionToPopup( QAction *action ); void createActions(); void createShortcutActions(); // GUI bool isStereoLinked() const Q_DECL_OVERRIDE { return m_linked; } void setStereoLinked( bool value ) Q_DECL_OVERRIDE; void setLabeled( bool value ) Q_DECL_OVERRIDE; void setTicks( bool ticks ) Q_DECL_OVERRIDE; void setIcons( bool value ) Q_DECL_OVERRIDE; QToolButton* addMediaButton(QString iconName, QLayout* layout, QWidget *parent); void updateMediaButton(); void setColors( QColor high, QColor low, QColor back ) Q_DECL_OVERRIDE; void setMutedColors( QColor high, QColor low, QColor back ) Q_DECL_OVERRIDE; bool eventFilter(QObject *obj, QEvent *ev) Q_DECL_OVERRIDE; QString iconName(); // Layout QSizePolicy sizePolicy() const; QSize sizeHint() const Q_DECL_OVERRIDE; - int labelExtentHint() const; - void setLabelExtent(int extent); + int labelExtentHint() const Q_DECL_OVERRIDE; + void setLabelExtent(int extent) Q_DECL_OVERRIDE; bool hasMuteButton() const; bool hasCaptureLED() const; static bool debugMe; public slots: void toggleRecsrc(); void toggleMuted(); void toggleStereoLinked(); void setDisabled( bool value ) Q_DECL_OVERRIDE; void update() Q_DECL_OVERRIDE; void showMoveMenu(); void showContextMenu( const QPoint &pos = QCursor::pos() ) Q_DECL_OVERRIDE; void increaseOrDecreaseVolume(bool arg1, Volume::VolumeTypeFlag volumeType); VolumeSliderExtraData& extraData(QAbstractSlider *slider); void addMediaControls(QBoxLayout* arg1); private slots: void setRecsrc(bool value); void setMuted(bool value); void volumeChange( int ); void sliderPressed(); void sliderReleased(); void increaseVolume(); void decreaseVolume(); void moveStreamAutomatic(); void moveStream( QString destId ); void mediaPlay(bool); void mediaNext(bool); void mediaPrev(bool); private: void createWidgets( bool showMuteLED, bool showCaptureLED, bool includeMixer ); void addSliders( QBoxLayout *volLayout, char type, Volume& vol, QList& ref_sliders, QString tooltipText ); // Methods that are called two times from a wrapper. Once for playabck, once for capture void setStereoLinkedInternal( QList< QAbstractSlider* >& ref_sliders, bool showSubcontrolLabels); void setTicksInternal( QList< QAbstractSlider* >& ref_sliders, bool ticks ); void volumeChangeInternal(Volume& vol, QList< QAbstractSlider* >& ref_sliders ); void updateInternal(Volume& vol, QList< QAbstractSlider* >& ref_sliders, bool muted); #ifndef QT_NO_ACCESSIBILITY void updateAccesability(); #endif QString calculatePlaybackIcon(MediaController::PlayState playState); QWidget *guiAddButtonSpacer(); void guiAddCaptureButton(const QString &captureTooltipText); void guiAddMuteButton(const QString &muteTooltipText); void guiAddControlIcon(const QString &tooltipText); void guiAddControlLabel(Qt::Alignment alignment, const QString &channelName); void addGlobalShortcut(QAction* action, const QString& label, bool dynamicControl); QSize controlButtonSize(); bool m_linked; QGridLayout *m_controlGrid; QLabel *m_controlIcon; QLabel *m_controlLabel; // is either QLabel or VerticalText ToggleToolButton *m_muteButton; ToggleToolButton *m_captureButton; QToolButton *m_mediaPlayButton; QSize m_controlButtonSize; 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.h b/gui/mixdevicewidget.h index 8e919feb..e0e200ae 100644 --- a/gui/mixdevicewidget.h +++ b/gui/mixdevicewidget.h @@ -1,92 +1,95 @@ //-*-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 "core/mixdevice.h" #include "core/volume.h" #include "gui/viewbase.h" class KActionCollection; class KShortcutsDialog; class MixDevice; class ProfControl; class MixDeviceWidget : public QWidget { Q_OBJECT public: MixDeviceWidget( shared_ptr md, bool small, QWidget* parent, ViewBase*, ProfControl * ); virtual ~MixDeviceWidget() = default; void addActionToPopup( QAction *action ); 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 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; /** * 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); protected: void contextMenuEvent(QContextMenuEvent *ev) Q_DECL_OVERRIDE; Qt::Orientation orientation() const { return (m_view->orientation()); } protected: shared_ptr m_mixdevice; KActionCollection* _mdwActions; KActionCollection* _mdwPopupActions; ViewBase* m_view; ProfControl* _pctl; bool m_small; KShortcutsDialog* m_shortcutsDialog; }; #endif diff --git a/gui/viewsliders.cpp b/gui/viewsliders.cpp index 2b089c27..366408d3 100644 --- a/gui/viewsliders.cpp +++ b/gui/viewsliders.cpp @@ -1,407 +1,428 @@ /* * 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 "viewsliders.h" // KMix #include "core/ControlManager.h" #include "core/mixdevicecomposite.h" #include "core/mixer.h" #include "gui/guiprofile.h" #include "gui/mdwenum.h" #include "gui/mdwslider.h" // KDE #include #include // Qt #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, const QString &id, Mixer *mixer, ViewBase::ViewFlags vflags, const QString &guiProfileId, KActionCollection *actColl) : ViewBase(parent, id, Qt::FramelessWindowHint, vflags, guiProfileId, actColl) { addMixer(mixer); 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 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(const shared_ptr md) { MixDeviceWidget *mdw; if (md->isEnum()) // control is a switch { mdw = new MDWEnum(md, // MixDevice (parameter) this, // parent this, // View widget md->controlProfile()); // profile 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 this, // parent this, // View widget md->controlProfile()); // profile m_layoutSliders->addWidget(mdw); } return (mdw); } void ViewSliders::initLayout() { resetMdws(); // 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 (m_layoutSliders!=nullptr) { QLayoutItem *li; while ((li = m_layoutSliders->takeAt(0))!=nullptr) delete li; m_layoutSliders = nullptr; } delete m_configureViewButton; m_configureViewButton = nullptr; if (m_layoutSwitches!=nullptr) { QLayoutItem *li; while ((li = m_layoutSwitches->takeAt(0))!=nullptr) delete li; m_layoutSwitches = nullptr; } 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 (orientation()==Qt::Horizontal) // horizontal sliders { m_layoutMDW->setAlignment(Qt::AlignLeft|Qt::AlignTop); m_layoutSliders = new QVBoxLayout(); m_layoutSliders->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); } else // vertical sliders { m_layoutMDW->setAlignment(Qt::AlignHCenter|Qt::AlignTop); m_layoutSliders = new QHBoxLayout(); m_layoutSliders->setAlignment(Qt::AlignHCenter|Qt::AlignTop); } m_layoutSliders->setContentsMargins(0, 0, 0, 0); m_layoutSliders->setSpacing(0); 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. 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 #endif // 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. const GUIProfile *guiprof = guiProfile(); if (guiprof!=nullptr) { foreach (Mixer *mixer, _mixers) { const MixSet &mixset = mixer->getMixSet(); 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; i 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") { // For temporary test mds.append(md); } #endif // 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() { + 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(); } void ViewSliders::configurationUpdate() { - // Adjust height of top part by setting it to the maximum of all mdw's - int labelExtent = 0; + // 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. - // Find out whether any MDWSlider has Switches. If one has, then we need "extents" + // The maximum extent is calculated and set separately for sliders and + // for switches (enums). + int labelExtentSliders = 0; + int labelExtentSwitches = 0; const int num = mixDeviceCount(); + + // Pass 1: Set the visibility of all controls for (int i = 0; i(mixDeviceAt(i)); - if (mdw!=nullptr && mdw->isVisibleTo(this)) - { - labelExtent = qMax(labelExtent, mdw->labelExtentHint()); - //qCDebug(KMIX_LOG) << "########## EXTENT for " << id() << " is " << labelExtent; - } + 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); } - //qCDebug(KMIX_LOG) << "topPartExtent is " << topPartExtent; + // Pass 2: Find the maximum extent of all applicable controls for (int i = 0; i(mixDeviceAt(i)); - if (mdw!=nullptr) - { - // guiLevel has been set earlier, by inspecting the controls - ProfControl *matchingControl = findMdw(mdw->mixDevice()->id()); - mdw->setVisible(matchingControl!=nullptr); + const QWidget *w = mixDeviceAt(i); + Q_ASSERT(w!=nullptr); + if (!w->isVisibleTo(this)) continue; // not currently visible - MDWSlider *mdwSlider = qobject_cast(mdw); - if (mdwSlider!=nullptr && labelExtent>0) - { - // additional options for sliders - mdwSlider->setLabelExtent(labelExtent); - } - } // if have a MixDeviceWidget - } // for all MDW's + 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()); + } + + // Pass 3: Set the maximum extent of all applicable controls + for (int i = 0; iisVisibleTo(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); + } + // 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() { const int num = mixDeviceCount(); for (int i = 0; i(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()); }