diff --git a/core/mixdevice.h b/core/mixdevice.h index f563832c..e816d7b6 100644 --- a/core/mixdevice.h +++ b/core/mixdevice.h @@ -1,263 +1,263 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MixDevice_h #define MixDevice_h #include #if defined(HAVE_STD_SHARED_PTR) #include using std::shared_ptr; #elif defined(HAVE_STD_TR1_SHARED_PTR) #include using std::tr1::shared_ptr; #endif //KMix #include "core/MediaController.h" class Mixer; class MixSet; class ProfControl; #include "core/volume.h" #include "kmixcore_export.h" class DBusControlWrapper; // KDE #include #include // Qt #include #include #include /** * This is the abstraction of a single control of a sound card, e.g. the PCM control. A control * can contain the 5 following subcontrols: playback-volume, capture-volume, playback-switch, * capture-switch and enumeration. The class is called MixDevice for historical reasons. Today it is just the Synonym for "Control". Design hint: In the past I (esken) considered merging the MixDevice and Volume classes. I finally decided against it, as it seems better to have the MixDevice being the container for the embedded subcontrol(s). These could be either Volume, Enum or some virtual MixDevice. */ class KMIXCORE_EXPORT MixDevice : public QObject { Q_OBJECT public: // For each ChannelType a special icon exists enum ChannelType { AUDIO = 1, BASS, CD, EXTERNAL, MICROPHONE, MIDI, RECMONITOR, TREBLE, UNKNOWN, VOLUME, VIDEO, SURROUND, HEADPHONE, DIGITAL, AC97, SURROUND_BACK, SURROUND_LFE, SURROUND_CENTERFRONT, SURROUND_CENTERBACK, SPEAKER, MICROPHONE_BOOST, MICROPHONE_FRONT_BOOST, MICROPHONE_FRONT, KMIX_COMPOSITE, APPLICATION_STREAM, // Some specific applications APPLICATION_AMAROK, APPLICATION_BANSHEE, APPLICATION_XMM2, APPLICATION_TOMAHAWK, APPLICATION_CLEMENTINE, // Hint: VLC still has compatibility problems: // 2.0 is not detected // 2.2-nightly has volume issues (total overdrive) APPLICATION_VLC, }; enum SwitchType { OnOff, Mute, Capture, Activator }; /** * Constructor for a MixDevice. * After having constructed a MixDevice, you must add it to the ControlPool * by calling addToPool(). You may then not delete this object. * * @par mixer The mixer this control belongs to * @par id Defines the ID, e.g. used in looking up the keys in kmixrc. Also it is used heavily inside KMix as unique key. * It is advised to set a nice name, like 'PCM:2', which would mean * "2nd PCM device of the sound card". The ID's may NOT contain whitespace. * The Creator (normally the backend) MUST pass distinct ID's for each MixDevices of one card. * * Virtual Controls (controls not created by a backend) are prefixed with "KMix::", e.g. * "KMix::RecSelector:0" * @par name is the readable name. This one is presented to the user in the GUI * @par type The control type. It is only used to find an appropriate icon */ MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type ); MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName = "", MixSet* moveDestinationMixSet = 0 ); ~MixDevice(); void close(); shared_ptr addToPool(); const QString& iconName() const { return _iconName; } void addPlaybackVolume(Volume &playbackVol); void addCaptureVolume (Volume &captureVol); void addEnums (QList& ref_enumList); // Media controls. New for KMix 4.0 MediaController* getMediaController(); // TODO move all media player controls to the MediaController class int mediaPlay(); int mediaPrev(); int mediaNext(); // Returns a user readable name of the control. QString readableName() { return _name; } // Sets a user readable name for the control. - void setReadableName(QString& name) { _name = name; } + void setReadableName(const QString &name) { _name = name; } QString configGroupName(QString prefix); /** * Returns an ID of this MixDevice, as passed in the constructor. The Creator (normally the backend) * MUST ensure that all MixDevices's of one card have unique ID's. * The ID is used through the whole KMix application (including the config file) for identifying controls. */ const QString& id() const; QString getFullyQualifiedId(); /** * Returns the DBus path for this MixDevice */ const QString dbusPath(); // Returns the associated mixer Mixer* mixer() { return _mixer; } // operator==() is used currently only for duplicate detection with QList's contains() method bool operator==(const MixDevice& other) const; // Methods for handling the switches. This methods are useful, because the Switch in the Volume object // is an abstract concept. It places no interpretation on the meaning of the switch (e.g. does "switch set" mean // "mute on", or does it mean "playback on", or "Capture active", or ... virtual bool isMuted(); virtual bool isVirtuallyMuted(); virtual void setMuted(bool value); virtual bool hasMuteSwitch(); virtual void toggleMute(); virtual bool isRecSource(); virtual bool isNotRecSource(); virtual void setRecSource(bool value); virtual bool isEnum(); /** * Returns whether this is an application stream. */ virtual bool isApplicationStream() const { return _applicationStream; }; /** * Mark this MixDevice as application stream */ void setApplicationStream(bool applicationStream) { _applicationStream = applicationStream; } bool isMovable() const { return (0 != _moveDestinationMixSet); } MixSet *getMoveDestinationMixSet() const { return _moveDestinationMixSet; } bool isArtificial() const { return _artificial; } void setArtificial(bool artificial) { _artificial = artificial; } void setControlProfile(ProfControl* control); ProfControl* controlProfile(); virtual Volume& playbackVolume(); virtual Volume& captureVolume(); void setEnumId(int); unsigned int enumId(); QList& enumValues(); bool hasPhysicalMuteSwitch(); bool read( KConfig *config, const QString& grp ); bool write( KConfig *config, const QString& grp ); int getUserfriendlyVolumeLevel(); void increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType); protected: void init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ); private: QString getVolString(Volume::ChannelID chid, bool capture); Mixer *_mixer; Volume _playbackVolume; Volume _captureVolume; int _enumCurrentId; QList _enumValues; // A MixDevice, that is an ENUM, has these _enumValues DBusControlWrapper *_dbusControlWrapper; MediaController* mediaController; // A virtual control. It will not be saved/restored and/or doesn't get shortcuts // Actually we discriminate those "virtual" controls in artificial controls and dynamic controls: // Type Shortcut Restore // Artificial: yes no Virtual::GlobalMaster or Virtual::CaptureGroup_3 (controls that are constructed artificially from other controls) // Dynamic : no no Controls that come and go, like Pulse Stream controls bool _artificial; MixSet *_moveDestinationMixSet; QString _iconName; bool _applicationStream; QString _name; // Channel name QString _id; // Primary key, used as part in config file keys ProfControl *_profControl; void readPlaybackOrCapture(const KConfigGroup& config, bool capture); void writePlaybackOrCapture(KConfigGroup& config, bool capture); }; #endif diff --git a/gui/dialogviewconfiguration.cpp b/gui/dialogviewconfiguration.cpp index 33888ae7..90aa8e29 100644 --- a/gui/dialogviewconfiguration.cpp +++ b/gui/dialogviewconfiguration.cpp @@ -1,455 +1,455 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dialogviewconfiguration.h" #include #include #include #include #include #include #include #include "gui/guiprofile.h" #include "gui/mixdevicewidget.h" #include "core/ControlManager.h" #include "core/mixdevice.h" #include "core/mixer.h" DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent) : QListWidgetItem(parent) { qCDebug(KMIX_LOG) << "DialogViewConfigurationItem() default constructor"; refreshItem(); } DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, QString id, bool shown, QString name, int splitted, const QString& iconName) : QListWidgetItem(parent), _id(id), _shown(shown), _name(name), _splitted(splitted), _iconName(iconName) { refreshItem(); } void DialogViewConfigurationItem::refreshItem() { setFlags((flags() | Qt::ItemIsDragEnabled) & ~Qt::ItemIsDropEnabled); setText(_name); setIcon(KIconLoader::global()->loadIcon( _iconName, KIconLoader::Small, IconSize(KIconLoader::Toolbar) )); setData(Qt::ToolTipRole, _id); // a hack. I am giving up to do it right setData(Qt::DisplayRole, _name); } /** * Serializer. Used for DnD. */ static QDataStream & operator<< ( QDataStream & s, const DialogViewConfigurationItem & item ) { s << item._id; s << item._shown; s << item._name; s << item._splitted; s << item._iconName; //qCDebug(KMIX_LOG) << "<< serialize << " << s; return s; } /** * Deserializer. Used for DnD. */ static QDataStream & operator>> ( QDataStream & s, DialogViewConfigurationItem & item ) { QString id; s >> id; item._id = id; bool shown; s >> shown; item._shown = shown; QString name; s >> name; item._name = name; int splitted; s >> splitted; item._splitted = splitted; QString iconName; s >> iconName; item._iconName = iconName; //qCDebug(KMIX_LOG) << ">> deserialize >> " << id << name << iconName; return s; } DialogViewConfigurationWidget::DialogViewConfigurationWidget(QWidget *parent) : QListWidget(parent), m_activeList(true) { setDragDropMode(QAbstractItemView::DragDrop); setDropIndicatorShown(true); setAcceptDrops(true); setSelectionMode(QAbstractItemView::SingleSelection); setDragEnabled(true); viewport()->setAcceptDrops(true); setAlternatingRowColors(true); } QMimeData* DialogViewConfigurationWidget::mimeData(const QList items) const { if (items.isEmpty()) return 0; QMimeData* mimedata = new QMimeData(); DialogViewConfigurationItem* item = 0; QByteArray data; { QDataStream stream(&data, QIODevice::WriteOnly); // we only support single selection item = static_cast(items.first()); stream << *item; } bool active = isActiveList(); mimedata->setData("application/x-kde-action-list", data); mimedata->setData("application/x-kde-source-treewidget", active ? "active" : "inactive"); return mimedata; } bool DialogViewConfigurationWidget::dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction /*action*/) { const QByteArray data = mimeData->data("application/x-kde-action-list"); if (data.isEmpty()) return false; QDataStream stream(data); const bool sourceIsActiveList = mimeData->data("application/x-kde-source-treewidget") == "active"; DialogViewConfigurationItem* item = new DialogViewConfigurationItem(0); // needs parent, use this temporarily stream >> *item; item->refreshItem(); emit dropped(this, index, item, sourceIsActiveList); return true; } DialogViewConfiguration::DialogViewConfiguration(QWidget *parent, ViewBase &view) : DialogBase(parent), _view(view) { setWindowTitle( i18n( "Configure Channels" ) ); setButtons( QDialogButtonBox::Ok|QDialogButtonBox::Cancel ); QWidget *frame = new QWidget( this ); frame->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); setMainWidget( frame ); // The _layout will hold two items: The title and the Drag-n-Drop area QVBoxLayout *layout = new QVBoxLayout(frame); // --- HEADER --- QLabel *qlb = new QLabel( i18n("Configure the visible channels. Drag icons between the lists to update."), frame ); layout->addWidget(qlb); _glayout = new QGridLayout(); _glayout->setMargin(0); layout->addLayout(_glayout); _qlw = 0; _qlwInactive = 0; createPage(); } /** * Drop an item from one list to the other */ void DialogViewConfiguration::slotDropped ( DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList ) { //qCDebug(KMIX_LOG) << "dropped item (index" << index << "): " << item->_id << item->_shown << item->_name << item->_splitted << item->_iconName; if ( list == _qlw ) { //DialogViewConfigurationItem* after = index > 0 ? static_cast(list->item(index-1)) : 0; //qCDebug(KMIX_LOG) << "after" << after->text() << after->internalTag(); if ( sourceIsActiveList ) { // has been dragged within the active list (moved). _qlw->insertItem ( index, item ); //moveActive(item, after); } else { // dragged from the inactive list to the active list _qlw->insertItem ( index, item ); //insertActive(item, after, true); } } else if ( list == _qlwInactive ) { // has been dragged to the inactive list -> remove from the active list. //removeActive(item); _qlwInactive->insertItem ( index, item ); } } void DialogViewConfiguration::addSpacer(int row, int col) { QWidget *dummy = new QWidget(); dummy->setFixedWidth(4); _glayout->addWidget(dummy,row,col); } void DialogViewConfiguration::moveSelection(DialogViewConfigurationWidget* from, DialogViewConfigurationWidget* to) { foreach ( QListWidgetItem* item, from->selectedItems() ) { QListWidgetItem *clonedItem = item->clone(); to->addItem ( clonedItem ); to->setCurrentItem(clonedItem); delete item; } } void DialogViewConfiguration::moveSelectionToActiveList() { moveSelection(_qlwInactive, _qlw); } void DialogViewConfiguration::moveSelectionToInactiveList() { moveSelection(_qlw, _qlwInactive); } void DialogViewConfiguration::selectionChangedActive() { // bool activeIsNotEmpty = _qlw->selectedItems().isEmpty(); moveRightButton->setEnabled(! _qlw->selectedItems().isEmpty()); moveLeftButton->setEnabled(false); } void DialogViewConfiguration::selectionChangedInactive() { moveLeftButton->setEnabled(! _qlwInactive->selectedItems().isEmpty()); moveRightButton->setEnabled(false); } /** * Create basic widgets of the Dialog. */ void DialogViewConfiguration::createPage() { QLabel *l1 = new QLabel( i18n("Visible channels:") ); _glayout->addWidget(l1,0,0); QLabel *l2 = new QLabel( i18n("Available channels:") ); _glayout->addWidget(l2,0,6); QWidget *frame = mainWidget(); _qlwInactive = new DialogViewConfigurationWidget(frame); _qlwInactive->setDragDropMode(QAbstractItemView::DragDrop); _qlwInactive->setActiveList(false); _glayout->addWidget(_qlwInactive,1,6); connect(_qlwInactive, SIGNAL(dropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool)), this , SLOT(slotDropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool))); addSpacer(1,1); const QIcon& icon = QIcon::fromTheme( QLatin1String( "arrow-left" )); moveLeftButton = new QPushButton(icon, ""); moveLeftButton->setEnabled(false); moveLeftButton->setToolTip(i18n("Move the selected channel to the visible list")); _glayout->addWidget(moveLeftButton,1,2); connect(moveLeftButton, SIGNAL(clicked(bool)), SLOT(moveSelectionToActiveList())); addSpacer(1,3); const QIcon& icon2 = QIcon::fromTheme( QLatin1String( "arrow-right" )); moveRightButton = new QPushButton(icon2, ""); moveRightButton->setEnabled(false); moveRightButton->setToolTip(i18n("Move the selected channel to the available (hidden) list")); _glayout->addWidget(moveRightButton,1,4); connect(moveRightButton, SIGNAL(clicked(bool)), SLOT(moveSelectionToInactiveList())); addSpacer(1,5); _qlw = new DialogViewConfigurationWidget(frame); _glayout->addWidget(_qlw,1,0); connect(_qlw , SIGNAL(dropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool)), this , SLOT(slotDropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool))); // --- CONTROLS IN THE GRID ------------------------------------ //QPalette::ColorRole bgRole; 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; 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; if ( mdw->isVisible() ) { new DialogViewConfigurationItem(_qlw, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); } else { new DialogViewConfigurationItem(_qlwInactive, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); } /* if ( ! md->isEnum() && ( ( md->playbackVolume().count() > 1) || ( md->captureVolume().count() > 1) ) ) { cb = new QCheckBox( "", vboxForScrollView ); // split cb->setBackgroundRole(bgRole); cb->setAutoFillBackground(true); _qSplitCB.append(cb); cb->setChecked( ! mdw->isStereoLinked() ); grid->addWidget(cb,1+i,1); } else { _qSplitCB.append(0); } */ /* if ( ! md->isEnum() && ( md->playbackVolume().count() + md->captureVolume().count() >0 ) ) { cb = new QCheckBox( "", vboxForScrollView ); // limit cb->setBackgroundRole(bgRole); cb->setAutoFillBackground(true); _qLimitCB.append(cb); grid->addWidget(cb,1+i,2); } else { */ //_qLimitCB.append(0); /*}*/ } // for all MDW's connect(_qlwInactive, SIGNAL(itemSelectionChanged()), this , SLOT(selectionChangedInactive())); connect(_qlw, SIGNAL(itemSelectionChanged()), this , SLOT(selectionChangedActive())); // scrollArea->updateGeometry(); updateGeometry(); connect(this, SIGNAL(accepted()), this, SLOT(apply())); #ifndef QT_NO_ACCESSIBILITY moveLeftButton->setAccessibleName( i18n("Show the selected channel") ); moveRightButton->setAccessibleName( i18n("Hide the selected channel") ); _qlw->setAccessibleName( i18n("Visible channels") ); _qlwInactive->setAccessibleName( i18n("Available channels") ); #endif } DialogViewConfiguration::~DialogViewConfiguration() { } void DialogViewConfiguration::apply() { // --- We have a 3-Step Apply of the Changes ------------------------------- // -1- Update view and profile ***************************************** GUIProfile* prof = _view.guiProfile(); - GUIProfile::ControlSet& oldControlset = prof->getControls(); + const GUIProfile::ControlSet &oldControlset = prof->getControls(); GUIProfile::ControlSet newControlset; QAbstractItemModel* model; model = _qlw->model(); prepareControls(model, true, oldControlset, newControlset); model = _qlwInactive->model(); prepareControls(model, false, oldControlset, newControlset); // -2- Copy all mandatory "catch-all" controls form the old to the new ControlSet ******* foreach ( ProfControl* pctl, oldControlset) { if ( pctl->isMandatory() ) { ProfControl* newCtl = new ProfControl(*pctl); // The user has selected controls => mandatory controls (RegExp templates) should not been shown any longer - newCtl->setVisible(GuiVisibility::GuiNEVER); + newCtl->setVisibility(GuiVisibility::Never); newControlset.push_back(newCtl); } } prof->setControls(newControlset); prof->finalizeProfile(); prof->setDirty(); // --- Step 3: Tell the view, that it has changed (probably it needs some "polishing" --- if ( _view.getMixers().size() == 1 ) ControlManager::instance().announce(_view.getMixers().first()->id(), ControlManager::ControlList, QString("View Configuration Dialog")); else ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("View Configuration Dialog")); } -void DialogViewConfiguration::prepareControls(QAbstractItemModel* model, bool isActiveView, GUIProfile::ControlSet& oldCtlSet, GUIProfile::ControlSet& newCtlSet) +void DialogViewConfiguration::prepareControls(QAbstractItemModel* model, bool isActiveView, const GUIProfile::ControlSet &oldCtlSet, GUIProfile::ControlSet &newCtlSet) { 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); + QRegExp idRegexp(control->id()); if ( ctlId.contains(idRegexp) ) { // found. Create a copy ProfControl* newCtl = new ProfControl(*control); - newCtl->id = '^' + ctlId + '$'; // Replace the (possible generic) regexp by the actual ID + newCtl->setId('^' + ctlId + '$'); // Replace the (possible generic) regexp by the actual ID // We have made this an an actual control. As it is derived (from e.g. ".*") it is NOT mandatory. newCtl->setMandatory(false); newCtl->setVisible(isActiveView); newCtlSet.push_back(newCtl); // qCDebug(KMIX_LOG) << "Added to new ControlSet (done): " << newCtl->id; break; } } } } diff --git a/gui/dialogviewconfiguration.h b/gui/dialogviewconfiguration.h index e58cd982..078e4b94 100644 --- a/gui/dialogviewconfiguration.h +++ b/gui/dialogviewconfiguration.h @@ -1,137 +1,137 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DIALOGVIEWCONFIGURATION_H #define DIALOGVIEWCONFIGURATION_H // Qt #include class QLabel; #include class QHBoxLayout; #include #include class QScrollArea; class QVBoxLayout; // Qt DND #include #include // KMix #include "dialogbase.h" #include "gui/guiprofile.h" #include "viewbase.h" class DialogViewConfigurationItem : public QListWidgetItem { friend class QDataStream; public: explicit DialogViewConfigurationItem( QListWidget *parent); DialogViewConfigurationItem( QListWidget *parent, QString id, bool shown, QString name, int splitted, const QString& iconName ); void refreshItem(); public: QString _id; bool _shown; QString _name; int _splitted; QString _iconName; }; class DialogViewConfigurationWidget : public QListWidget { Q_OBJECT public: explicit DialogViewConfigurationWidget(QWidget *parent=0); void setActiveList(bool isActiveList) { m_activeList = isActiveList; } bool isActiveList() const { return m_activeList; }; Q_SIGNALS: void dropped(DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList); protected: QMimeData* mimeData(const QList items) const Q_DECL_OVERRIDE; bool dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction action) Q_DECL_OVERRIDE; Qt::DropActions supportedDropActions() const Q_DECL_OVERRIDE { //qCDebug(KMIX_LOG) << "supportedDropActions!"; return Qt::MoveAction; } QStringList mimeTypes() const Q_DECL_OVERRIDE { //qCDebug(KMIX_LOG) << "mimeTypes!"; return QStringList() << "application/x-kde-action-list"; } // Skip internal dnd handling in QListWidget ---- how is one supposed to figure this out // without reading the QListWidget code !? void dropEvent(QDropEvent* ev) Q_DECL_OVERRIDE { QAbstractItemView::dropEvent(ev); } private: bool m_activeList; }; class DialogViewConfiguration : public DialogBase { Q_OBJECT public: DialogViewConfiguration(QWidget* parent, ViewBase& view); ~DialogViewConfiguration(); public slots: void apply(); private slots: void slotDropped(DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList ); void moveSelectionToActiveList(); void moveSelectionToInactiveList(); void selectionChangedActive(); void selectionChangedInactive(); private: //void dragEnterEvent(QDragEnterEvent *event); - void prepareControls(QAbstractItemModel* model, bool isActiveView, GUIProfile::ControlSet& oldCtlSet, GUIProfile::ControlSet& newCtlSet); + void prepareControls(QAbstractItemModel* model, bool isActiveView, const GUIProfile::ControlSet &oldCtlSet, GUIProfile::ControlSet &newCtlSet); void createPage(); void addSpacer(int row, int col); void moveSelection(DialogViewConfigurationWidget* from, DialogViewConfigurationWidget* to); ViewBase& _view; QGridLayout *_glayout; QPushButton* moveLeftButton; QPushButton* moveRightButton; DialogViewConfigurationWidget *_qlw; DialogViewConfigurationWidget *_qlwInactive; }; #endif diff --git a/gui/guiprofile.cpp b/gui/guiprofile.cpp index b714b057..22d356f6 100644 --- a/gui/guiprofile.cpp +++ b/gui/guiprofile.cpp @@ -1,933 +1,878 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 2006-2007 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gui/guiprofile.h" // Qt #include #include #include #include // System #include #include // KMix #include "core/mixer.h" -QMap GUIProfile::s_profiles; +QMap s_profiles; -GuiVisibility const GuiVisibility::GuiSIMPLE (QString("simple" ) , GuiVisibility::SIMPLE); -GuiVisibility const GuiVisibility::GuiEXTENDED(QString("extended") , GuiVisibility::EXTENDED); -// For backwards compatibility, GuiFULL has the ID "all", and not "full" -GuiVisibility const GuiVisibility::GuiFULL (QString("all" ) , GuiVisibility::FULL); -GuiVisibility const GuiVisibility::GuiCUSTOM (QString("custom" ) , GuiVisibility::CUSTOM); -GuiVisibility const GuiVisibility::GuiNEVER (QString("never" ) , GuiVisibility::NEVER); -GuiVisibility const GuiVisibility::GuiDEFAULT (QString("default" ) , GuiVisibility::DEFAULT); + +static QString visibilityToString(GuiVisibility vis) +{ + switch (vis) + { +case GuiVisibility::Simple: return ("simple"); +case GuiVisibility::Extended: return ("extended"); +// For backwards compatibility, 'Full' has the ID "all" and not "full" +case GuiVisibility::Full: return ("all"); +case GuiVisibility::Custom: return ("custom"); +case GuiVisibility::Never: return ("never"); +case GuiVisibility::Default: return ("default"); +default: return ("unknown"); + } +} -bool SortedStringComparator::operator()(const std::string& s1, const std::string& s2) const { - return ( s1 < s2 ); +static GuiVisibility visibilityFromString(const QString &str) +{ + if (str=="simple") return (GuiVisibility::Simple); + else if (str=="extended") return (GuiVisibility::Extended); + else if (str=="all") return (GuiVisibility::Full); + else if (str=="custom") return (GuiVisibility::Custom); + else if (str=="never") return (GuiVisibility::Never); + + qCWarning(KMIX_LOG) << "Unknown string value" << str; + return (GuiVisibility::Full); } + /** * Product comparator for sorting: * We want the comparator to sort ascending by Vendor. "Inside" the Vendors, we sort by Product Name. */ bool ProductComparator::operator()(const ProfProduct* p1, const ProfProduct* p2) const { if ( p1->vendor < p2->vendor ) { return ( true ); } else if ( p1->vendor > p2->vendor ) { return ( false ); } else if ( p1->productName < p2->productName ) { return ( true ); } else if ( p1->productName > p2->productName ) { return ( false ); } else { /** * We reach this point, if vendor and product name is identical. * Actually we don't care about the order then, so we decide that "p1" comes first. * * (Hint: As this is a set comparator, the return value HERE doesn't matter that * much. But if we would decide later to change this Comparator to be a Map Comparator, * we must NOT return a "0" for identity - this would lead to non-insertion on insert()) */ return true; } } GUIProfile::GUIProfile() { _dirty = false; _driverVersionMin = 0; _driverVersionMax = 0; _generation = 1; } GUIProfile::~GUIProfile() { qCWarning(KMIX_LOG) << "Thou shalt not delete any GUI profile. This message is only OK, when quitting KMix"; qDeleteAll(_controls); qDeleteAll(_products); } /** * Clears the GUIProfile cache. You must only call this * before termination of the application, as GUIProfile instances are used in other classes, especially the views. * There is no need to call this in non-GUI applications like kmixd and kmixctrl. */ void GUIProfile::clearCache() { qDeleteAll(s_profiles); s_profiles.clear(); } - - -void GUIProfile::setId(const QString& id) -{ - _id = id; -} - -QString GUIProfile::getId() const -{ - return _id; -} - -bool GUIProfile::isDirty() const { - return _dirty; -} - -void GUIProfile::setDirty() { - _dirty = true; -} - /** * Build a profile name. Suitable to use as primary key and to build filenames. * @arg mixer The mixer * @arg profileName The profile name (e.g. "capture", "playback", "my-cool-profile", or "any" * @return The profile name */ -QString GUIProfile::buildProfileName(Mixer* mixer, QString profileName, bool ignoreCard) +static QString buildProfileName(Mixer *mixer, const QString &profileName, bool ignoreCard) { QString fname; fname += mixer->getDriverName(); if (!ignoreCard) { fname += ".%1.%2"; fname = fname.arg(mixer->getBaseName()).arg(mixer->getCardInstance()); } fname += '.' + profileName; fname.replace(' ','_'); return fname; } + /** * Generate a readable profile name (for presenting to the user). * Hint: Currently used as Tab label. */ -QString GUIProfile::buildReadableProfileName(Mixer* mixer, QString profileName) +static QString buildReadableProfileName(Mixer *mixer, const QString &profileName) { QString fname; fname += mixer->getBaseName(); if ( mixer->getCardInstance() > 1 ) { fname += " %1"; fname = fname.arg(mixer->getCardInstance()); } if ( profileName != "default" ) { fname += ' ' + profileName; } qCDebug(KMIX_LOG) << fname; return fname; } /** * Returns the GUIProfile for the given ID (= "fullyQualifiedName"). * If not found 0 is returned. There is no try to load it. * * @returns The loaded GUIProfile for the given ID */ -GUIProfile* GUIProfile::find(QString id) +GUIProfile *GUIProfile::find(const QString &id) { - // Not thread safe (due to non-atomic contains()/get() - if ( s_profiles.contains(id) ) - { - return s_profiles[id]; - } - else - { - return 0; - } + // Return found value or default-constructed one (nullptr). + // Does not insert into map. Now thread-safe. + return (s_profiles.value(id)); +} + + +static QString createNormalizedFilename(const QString &profileId) +{ + QString profileIdNormalized(profileId); + profileIdNormalized.replace(':', '.'); + + QString fileName("profiles/"); + fileName = fileName + profileIdNormalized + ".xml"; + return fileName; +} + + +/** + * Loads a GUI Profile from disk (XML profile file). + * It tries to load the Soundcard specific file first (a). + * If it doesn't exist, it will load the default profile corresponding to the soundcard driver (b). + */ +static GUIProfile *loadProfileFromXMLfiles(Mixer *mixer, const QString &profileName) +{ + GUIProfile* guiprof = 0; + QString fileName = createNormalizedFilename(profileName); + QString fileNameFQ = QStandardPaths::locate(QStandardPaths::DataLocation, fileName ); + + if ( ! fileNameFQ.isEmpty() ) { + guiprof = new GUIProfile(); + if ( guiprof->readProfile(fileNameFQ) && ( guiprof->match(mixer) > 0) ) { + // loaded + } + else { + delete guiprof; // not good (e.g. Parsing error => drop this profile silently) + guiprof = 0; + } + } + else { + qCDebug(KMIX_LOG) << "Ignore file " <getId()] = guiprof; + qCDebug(KMIX_LOG) << "I have added" << guiprof->getId() << "; Number of profiles is now " << s_profiles.size() ; } /** * Finds the correct profile for the given mixer. * If already loaded from disk, returns the cached version. * Otherwise load profile from disk: Priority: Card specific profile, Card unspecific profile * * @arg mixer The mixer * @arg profileName The profile name (e.g. "ALSA.X-Fi.default", or "OSS.intel-cha51.playback") * A special case is "", which means that a card specific name should be generated. * @arg profileNameIsFullyQualified If true, an exact match will be searched. Otherwise it is a simple name like "playback" or "capture" * @arg ignoreCardName If profileName not fully qualified, this is used in building the requestedProfileName * @return GUIProfile* The loaded GUIProfile, or 0 if no profile matched. Hint: if you use allowFallback==true, this should never return 0. */ -GUIProfile* GUIProfile::find(Mixer* mixer, QString profileName, bool profileNameIsFullyQualified, bool ignoreCardName) +GUIProfile *GUIProfile::find(Mixer *mixer, const QString &profileName, bool profileNameIsFullyQualified, bool ignoreCardName) { - GUIProfile* guiprof = 0; + GUIProfile *guiprof = nullptr; - if ( mixer == 0 || profileName.isEmpty() ) - return 0; + if (mixer==nullptr || profileName.isEmpty()) return (nullptr); // if ( mixer->isDynamic() ) { // qCDebug(KMIX_LOG) << "GUIProfile::find() Not loading GUIProfile for Dynamic Mixer (e.g. PulseAudio)"; // return 0; // } QString requestedProfileName; QString fullQualifiedProfileName; if ( profileNameIsFullyQualified ) { requestedProfileName = profileName; fullQualifiedProfileName = profileName; } else { requestedProfileName = buildProfileName(mixer, profileName, ignoreCardName); fullQualifiedProfileName = buildProfileName(mixer, profileName, false); } if ( s_profiles.contains(fullQualifiedProfileName) ) { guiprof = s_profiles.value(fullQualifiedProfileName); // Cached } else { guiprof = loadProfileFromXMLfiles(mixer, requestedProfileName); // Load from XML ###Card specific profile### - if ( guiprof != 0 ) { + if ( guiprof!=nullptr) { guiprof->_mixerId = mixer->id(); guiprof->setId(fullQualifiedProfileName); // this one contains some soundcard id (basename + instance) if ( guiprof->getName().isEmpty() ) { // If the profile didn't contain a name then lets define one guiprof->setName(buildReadableProfileName(mixer,profileName)); // The caller can rename this if he likes guiprof->setDirty(); } if ( requestedProfileName != fullQualifiedProfileName) { // This is very important! // When the final profileName (fullQualifiedProfileName) is different from // what we have loaded (requestedProfileName, e.g. "default"), we MUST // set the profile dirty, so it gets saved. Otherwise we would write the // fullQualifiedProfileName in the kmixrc, and will not find it on the next // start of KMix. guiprof->setDirty(); } addProfile(guiprof); } } - return guiprof; -} - -/* - * Add the profile to the internal list of profiles (Profile caching). - */ -void GUIProfile::addProfile(GUIProfile* guiprof) -{ - // Possible TODO: Delete old mapped GUIProfile, if it exists. Otherwise we might leak one GUIProfile instance - // per unplug/plug sequence. Its quite likely possible that currently no Backend leads to a - // leak: This is because they either don't hotplug cards (PulseAudio, MPRIS2), or they ship - // a XML gui profile (so the Cached version is retrieved, and addProfile() is not called). - - s_profiles[guiprof->getId()] = guiprof; - qCDebug(KMIX_LOG) << "I have added" << guiprof->getId() << "; Number of profiles is now " << s_profiles.size() ; + return (guiprof); } - - -/** - * Loads a GUI Profile from disk (xml profile file). - * It tries to load the Soundcard specific file first (a). - * If it doesn't exist, it will load the default profile corresponding to the soundcard driver (b). - */ -GUIProfile* GUIProfile::loadProfileFromXMLfiles(Mixer* mixer, QString profileName) -{ - GUIProfile* guiprof = 0; - QString fileName = createNormalizedFilename(profileName); - QString fileNameFQ = QStandardPaths::locate(QStandardPaths::DataLocation, fileName ); - - if ( ! fileNameFQ.isEmpty() ) { - guiprof = new GUIProfile(); - if ( guiprof->readProfile(fileNameFQ) && ( guiprof->match(mixer) > 0) ) { - // loaded - } - else { - delete guiprof; // not good (e.g. Parsing error => drop this profile silently) - guiprof = 0; - } - } - else { - qCDebug(KMIX_LOG) << "Ignore file " <vendor = mixer->getDriverName(); prd->productName = mixer->readableName(); prd->productRelease = "1.0"; fallback->_products.insert(prd); static QString matchAll(".*"); static QString matchAllSctl(".*"); ProfControl* ctl = new ProfControl(matchAll, matchAllSctl); //ctl->regexp = matchAll; // make sure id matches the regexp ctl->setMandatory(true); fallback->_controls.push_back(ctl); fallback->_soundcardDriver = mixer->getDriverName(); fallback->_soundcardName = mixer->readableName(); fallback->finalizeProfile(); fallback->_mixerId = mixer->id(); fallback->setId(fullQualifiedProfileName); // this one contains some soundcard id (basename + instance) fallback->setName(buildReadableProfileName(mixer, QString("default"))); // The caller can rename this if he likes fallback->setDirty(); /* -3- Add the profile to the static list * Hint: This looks like a memory leak, as we never remove profiles from memory while KMix runs. * Especially with application streams it looks suspicious. But please be aware that this method is only * called for soundcard hotplugs, and not on stream hotplugs. At least it is supposed to be like that. * * Please also see the docs at addProfile(), they also address the possible memory leakage. */ addProfile(fallback); return fallback; } /** * Fill the profile with the data from the given XML profile file. * @par ref_fileName: Full qualified filename (with path). * @return bool True, if the profile was successfully created. False if not (e.g. parsing error). */ bool GUIProfile::readProfile(const QString& ref_fileName) { QXmlSimpleReader *xmlReader = new QXmlSimpleReader(); qCDebug(KMIX_LOG) << "Read profile:" << ref_fileName ; QFile xmlFile( ref_fileName ); QXmlInputSource source( &xmlFile ); GUIProfileParser* gpp = new GUIProfileParser(this); xmlReader->setContentHandler(gpp); bool ok = xmlReader->parse( source ); //std::cout << "Raw Profile: " << *this; if ( ok ) { ok = finalizeProfile(); } // Read OK else { // !! this error message about faulty profiles should probably be surrounded with i18n() qCCritical(KMIX_LOG) << "ERROR: The profile '" << ref_fileName<< "' contains errors, and is not used."; } + + // TODO: can these 2 be stack variables? delete gpp; delete xmlReader; return ok; } -const QString GUIProfile::createNormalizedFilename(const QString& profileId) -{ - QString profileIdNormalized(profileId); - profileIdNormalized.replace(':', '.'); - - QString fileName("profiles/"); - fileName = fileName + profileIdNormalized + ".xml"; - return fileName; - } bool GUIProfile::writeProfile() { bool ret = false; QString profileId = getId(); QString fileName = createNormalizedFilename(profileId); QString fileNameFQ = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + '/' + fileName; qCDebug(KMIX_LOG) << "Write profile:" << fileNameFQ ; QFile f(fileNameFQ); if ( f.open(QIODevice::WriteOnly | QFile::Truncate) ) { QTextStream out(&f); out << *this; f.close(); ret = true; } if ( ret ) { _dirty = false; } return ret; } /** This is now empty. It can be removed */ bool GUIProfile::finalizeProfile() const { - bool ok = true; - return ok; + return (true); } // ------------------------------------------------------------------------------------- void GUIProfile::setControls(ControlSet& newControlSet) { qDeleteAll(_controls); _controls = newControlSet; } -const GUIProfile::ControlSet& GUIProfile::getControls() const -{ - return _controls; -} - -GUIProfile::ControlSet& GUIProfile::getControls() -{ - return _controls; -} - -void GUIProfile::addProduct(ProfProduct* prd) -{ - _products.insert(prd); -} - // ------------------------------------------------------------------------------------- /** * Returns how good the given Mixer matches this GUIProfile. * A value between 0 (not matching at all) and MAXLONG (perfect match) is returned. * * Here is the current algorithm: * * If the driver doesn't match, 0 is returned. (OK) * If the card-name ... (OK) * is "*", this is worth 1 point * doesn't match, 0 is returned. * matches, this is worth 500 points. * * If the "card type" ... * is empty, this is worth 0 points. !!! not implemented yet * doesn't match, 0 is returned. !!! not implemented yet * matches , this is worth 500 points. !!! not implemented yet * * If the "driver version" doesn't match, 0 is returned. !!! not implemented yet * If the "driver version" matches, this is worth ... * 4000 unlimited <=> "*:*" * 6000 toLower-bound-limited <=> "toLower-bound:*" * 6000 upper-bound-limited <=> "*:upper-bound" * 8000 upper- and toLower-bound limited <=> "toLower-bound:upper-bound" * or 10000 points (upper-bound=toLower-bound=bound <=> "bound:bound" * * The Profile-Generation is added to the already achieved points. (done) * The maximum gain is 900 points. * Thus you can create up to 900 generations (0-899) without "overriding" * the points gained from the "driver version" or "card-type". * * For example: card-name="*" (1), card-type matches (1000), * driver version "*:*" (4000), Profile-Generation 4 (4). * Sum: 1 + 1000 + 4000 + 4 = 5004 * * @todo Implement "card type" match value * @todo Implement "version" match value (must be in backends as well) */ -unsigned long GUIProfile::match(Mixer* mixer) { +unsigned long GUIProfile::match(Mixer* mixer) const +{ unsigned long matchValue = 0; if ( _soundcardDriver != mixer->getDriverName() ) { return 0; } if ( _soundcardName == "*" ) { matchValue += 1; } else if ( _soundcardName != mixer->getBaseName() ) { return 0; // card name does not match } else { matchValue += 500; // card name matches } // !!! we don't check current for the driver version. // So we assign simply 4000 points for now. matchValue += 4000; if ( _generation < 900 ) { matchValue += _generation; } else { matchValue += 900; } return matchValue; } -QString xmlify(QString raw); -QString xmlify(QString raw) +static QByteArray xmlify(const QString &str) { + QByteArray raw = str.toUtf8(); // qCDebug(KMIX_LOG) << "Before: " << raw; - raw = raw.replace('&', "&"); - raw = raw.replace('<', "<"); - raw = raw.replace('>', ">"); - raw = raw.replace('\'', "'"); - raw = raw.replace('\"', """); + raw.replace('&', "&"); + raw.replace('<', "<"); + raw.replace('>', ">"); + raw.replace('\'', "'"); + raw.replace('\"', """); // qCDebug(KMIX_LOG) << "After : " << raw; - return raw; + return (raw); } +// TODO: use QXmlStreamWriter QTextStream& operator<<(QTextStream &os, const GUIProfile& guiprof) { // qCDebug(KMIX_LOG) << "ENTER QTextStream& operator<<"; os << ""; os << endl << endl; - os << "" << endl << endl ; os << "" << endl; for ( GUIProfile::ProductSet::const_iterator it = guiprof._products.begin(); it != guiprof._products.end(); ++it) { ProfProduct* prd = *it; - os << "vendor).toUtf8().constData() << "\" name=\"" << xmlify(prd->productName).toUtf8().constData() << "\""; + os << "vendor) << "\" name=\"" << xmlify(prd->productName) << "\""; if ( ! prd->productRelease.isNull() ) { - os << " release=\"" << xmlify(prd->productRelease).toUtf8().constData() << "\""; + os << " release=\"" << xmlify(prd->productRelease) << "\""; } if ( ! prd->comment.isNull() ) { - os << " comment=\"" << xmlify(prd->comment).toUtf8().constData() << "\""; + os << " comment=\"" << xmlify(prd->comment) << "\""; } os << " />" << endl; } // for all products os << endl; foreach ( ProfControl* profControl, guiprof.getControls() ) { - os << "id).toUtf8().constData() << "\"" ; - if ( !profControl->name.isNull() && profControl->name != profControl->id ) { - os << " name=\"" << xmlify(profControl->name).toUtf8().constData() << "\"" ; + os << "id()) << "\"" ; + if ( !profControl->name().isNull() && profControl->name()!=profControl->id()) { + os << " name=\"" << xmlify(profControl->name()) << "\"" ; } - os << " subcontrols=\"" << xmlify( profControl->renderSubcontrols().toUtf8().constData()) << "\"" ; - os << " show=\"" << xmlify(profControl->getVisibility().getId().toUtf8().constData()) << "\"" ; + os << " subcontrols=\"" << xmlify( profControl->renderSubcontrols()) << "\"" ; + os << " show=\"" << xmlify(visibilityToString(profControl->getVisibility())) << "\"" ; if ( profControl->isMandatory() ) { os << " mandatory=\"true\""; } if ( profControl->isSplit() ) { os << " split=\"true\""; } os << " />" << endl; } // for all controls os << endl; os << "" << endl; // qCDebug(KMIX_LOG) << "EXIT QTextStream& operator<<"; return os; } -std::ostream& operator<<(std::ostream& os, const GUIProfile& guiprof) { - os << "Soundcard:" << std::endl - << " Driver=" << guiprof._soundcardDriver.toUtf8().constData() << std::endl - << " Driver-Version min=" << guiprof._driverVersionMin - << " max=" << guiprof._driverVersionMax << std::endl - << " Card-Name=" << guiprof._soundcardName.toUtf8().constData() << std::endl - << " Card-Type=" << guiprof._soundcardType.toUtf8().constData() << std::endl - << " Profile-Generation=" << guiprof._generation - << std::endl; - os << "Profile:" << std::endl - << " Id=" << guiprof._id.toUtf8().constData() << std::endl - << " Name=" << guiprof._name.toUtf8().constData() << std::endl; - - for ( GUIProfile::ProductSet::const_iterator it = guiprof._products.begin(); it != guiprof._products.end(); ++it) - { - ProfProduct* prd = *it; - os << "Product:\n Vendor=" << prd->vendor.toUtf8().constData() << std::endl << " Name=" << prd->productName.toUtf8().constData() << std::endl; - if ( ! prd->productRelease.isNull() ) { - os << " Release=" << prd->productRelease.toUtf8().constData() << std::endl; - } - if ( ! prd->comment.isNull() ) { - os << " Comment = " << prd->comment.toUtf8().constData() << std::endl; - } - } // for all products - - foreach ( ProfControl* profControl, guiprof.getControls() ) - { -// ProfControl* profControl = *it; - os << "Control:\n ID=" << profControl->id.toUtf8().constData() << std::endl; - if ( !profControl->name.isNull() && profControl->name != profControl->id ) { - os << " Name = " << profControl->name.toUtf8().constData() << std::endl; - } - os << " Subcontrols=" << profControl->renderSubcontrols().toUtf8().constData() << std::endl; - if ( profControl->isMandatory() ) { - os << " mandatory=\"true\"" << std::endl; - } - if ( profControl->isSplit() ) { - os << " split=\"true\"" << std::endl; - } - } // for all controls - - return os; -} - -ProfControl::ProfControl(QString& id, QString& subcontrols ) : - visibility(GuiVisibility::GuiSIMPLE), _mandatory(false), _split(false) +ProfControl::ProfControl(const QString &id, const QString &subcontrols) + : _id(id), + _visibility(GuiVisibility::Simple), + _mandatory(false), + _split(false) { - d = new ProfControlPrivate(); - this->id = id; setSubcontrols(subcontrols); } -ProfControl::ProfControl(const ProfControl &profControl) : - visibility(profControl.visibility), _mandatory(false), _split(false) +ProfControl::ProfControl(const ProfControl &profControl) + : _mandatory(false), + _split(false) { - d = new ProfControlPrivate(); - id = profControl.id; - name = profControl.name; + _id = profControl._id; + _name = profControl._name; + _visibility = profControl._visibility; _useSubcontrolPlayback = profControl._useSubcontrolPlayback; _useSubcontrolCapture = profControl._useSubcontrolCapture; _useSubcontrolPlaybackSwitch = profControl._useSubcontrolPlaybackSwitch; _useSubcontrolCaptureSwitch = profControl._useSubcontrolCaptureSwitch; _useSubcontrolEnum = profControl._useSubcontrolEnum; - d->subcontrols = profControl.d->subcontrols; + _subcontrols = profControl._subcontrols; - name = profControl.name; - backgroundColor = profControl.backgroundColor; - switchtype = profControl.switchtype; + _backgroundColor = profControl._backgroundColor; + _switchtype = profControl._switchtype; _mandatory = profControl._mandatory; _split = profControl._split; } -ProfControl::~ProfControl() { - delete d; + +bool ProfControl::satisfiesVisibility(GuiVisibility vis) const +{ + GuiVisibility me = getVisibility(); + if (me==GuiVisibility::Never || vis==GuiVisibility::Never) return (false); + if (me==GuiVisibility::Custom || vis==GuiVisibility::Custom) return (false); + if (vis==GuiVisibility::Default) return (true); + return (static_cast(me)<=static_cast(vis)); } + /** - * An overridden method for #setVisible(const GuiVisibility&), that either sets GuiVisibility::GuiSIMPLE - * or GuiVisibility::GuiNEVER; - * - * @param visible + * An overridden method that either sets + * GuiVisibility::Simple or GuiVisibility::Never. */ void ProfControl::setVisible(bool visible) { - this->visibility = visible ? GuiVisibility::GuiSIMPLE : GuiVisibility::GuiNEVER; + setVisibility(visible ? GuiVisibility::Simple : GuiVisibility::Never); } -void ProfControl::setVisible(const GuiVisibility& visibility) +void ProfControl::setVisibility(GuiVisibility vis) { - this->visibility = visibility; + _visibility = vis; +} + +void ProfControl::setVisibility(const QString &visString) +{ + setVisibility(visibilityFromString(visString)); } void ProfControl::setSubcontrols(QString sctls) { - d->subcontrols = sctls; + _subcontrols = sctls; _useSubcontrolPlayback = false; _useSubcontrolCapture = false; _useSubcontrolPlaybackSwitch = false; _useSubcontrolCaptureSwitch = false; _useSubcontrolEnum = false; QStringList qsl = sctls.split( ',', QString::SkipEmptyParts, Qt::CaseInsensitive); QStringListIterator qslIt(qsl); while (qslIt.hasNext()) { QString sctl = qslIt.next(); //qCDebug(KMIX_LOG) << "setSubcontrols found: " << sctl.toLocal8Bit().constData(); if ( sctl == "pvolume" ) _useSubcontrolPlayback = true; else if ( sctl == "cvolume" ) _useSubcontrolCapture = true; else if ( sctl == "pswitch" ) _useSubcontrolPlaybackSwitch = true; else if ( sctl == "cswitch" ) _useSubcontrolCaptureSwitch = true; else if ( sctl == "enum" ) _useSubcontrolEnum = true; else if ( sctl == "*" || sctl == ".*") { _useSubcontrolCapture = true; _useSubcontrolCaptureSwitch = true; _useSubcontrolPlayback = true; _useSubcontrolPlaybackSwitch = true; _useSubcontrolEnum = true; } else qCWarning(KMIX_LOG) << "Ignoring unknown subcontrol type '" << sctl << "' in profile"; } } QString ProfControl::renderSubcontrols() { QString sctlString; if ( _useSubcontrolPlayback && _useSubcontrolPlaybackSwitch && _useSubcontrolCapture && _useSubcontrolCaptureSwitch && _useSubcontrolEnum ) { return QString("*"); } else { if ( _useSubcontrolPlayback ) { sctlString += "pvolume,"; } if ( _useSubcontrolCapture ) { sctlString += "cvolume,"; } if ( _useSubcontrolPlaybackSwitch ) { sctlString += "pswitch,"; } if ( _useSubcontrolCaptureSwitch ) { sctlString += "cswitch,"; } if ( _useSubcontrolEnum ) { sctlString += "enum,"; } if ( sctlString.length() > 0 ) { sctlString.chop(1); } return sctlString; } } // ### PARSER START ################################################ GUIProfileParser::GUIProfileParser(GUIProfile* ref_gp) : _guiProfile(ref_gp) { _scope = GUIProfileParser::NONE; // no scope yet } bool GUIProfileParser::startDocument() { _scope = GUIProfileParser::NONE; // no scope yet return true; } bool GUIProfileParser::startElement( const QString& , const QString& , const QString& qName, const QXmlAttributes& attributes ) { switch ( _scope ) { case GUIProfileParser::NONE: /** we are reading the "top level" ***************************/ if ( qName.toLower() == "soundcard" ) { _scope = GUIProfileParser::SOUNDCARD; addSoundcard(attributes); } else { // skip unknown top-level nodes std::cerr << "Ignoring unsupported element '" << qName.toUtf8().constData() << "'" << std::endl; } // we are accepting only break; case GUIProfileParser::SOUNDCARD: if ( qName.toLower() == "product" ) { // Defines product names under which the chipset/hardware is sold addProduct(attributes); } else if ( qName.toLower() == "control" ) { addControl(attributes); } else if ( qName.toLower() == "profile" ) { addProfileInfo(attributes); } else { std::cerr << "Ignoring unsupported element '" << qName.toUtf8().constData() << "'" << std::endl; } // we are accepting , and break; } // switch() return true; } bool GUIProfileParser::endElement( const QString&, const QString&, const QString& qName ) { if ( qName == "soundcard" ) { _scope = GUIProfileParser::NONE; // should work out OK, as we don't nest soundcard entries } return true; } void GUIProfileParser::addSoundcard(const QXmlAttributes& attributes) { /* std::cout << "Soundcard: "; printAttributes(attributes); */ QString driver = attributes.value("driver"); QString version = attributes.value("version"); QString name = attributes.value("name"); QString type = attributes.value("type"); QString generation = attributes.value("generation"); if ( !driver.isNull() && !name.isNull() ) { _guiProfile->_soundcardDriver = driver; _guiProfile->_soundcardName = name; if ( type.isNull() ) { _guiProfile->_soundcardType = ""; } else { _guiProfile->_soundcardType = type; } if ( version.isNull() ) { _guiProfile->_driverVersionMin = 0; _guiProfile->_driverVersionMax = 0; } else { std::pair versionMinMax; splitPair(version, versionMinMax, ':'); _guiProfile->_driverVersionMin = versionMinMax.first.toULong(); _guiProfile->_driverVersionMax = versionMinMax.second.toULong(); } if ( type.isNull() ) { type = ""; }; if ( generation.isNull() ) { _guiProfile->_generation = 0; } else { // Hint: If the conversion fails, _generation will be assigned 0 (which is fine) _guiProfile->_generation = generation.toUInt(); } } } void GUIProfileParser::addProfileInfo(const QXmlAttributes& attributes) { QString name = attributes.value("name"); QString id = attributes.value("id"); _guiProfile->setId(id); _guiProfile->setName(name); } void GUIProfileParser::addProduct(const QXmlAttributes& attributes) { /* std::cout << "Product: "; printAttributes(attributes); */ QString vendor = attributes.value("vendor"); QString name = attributes.value("name"); QString release = attributes.value("release"); QString comment = attributes.value("comment"); if ( !vendor.isNull() && !name.isNull() ) { // Adding a product makes only sense if we have at least vendor and product name ProfProduct *prd = new ProfProduct(); prd->vendor = vendor; prd->productName = name; prd->productRelease = release; prd->comment = comment; _guiProfile->addProduct(prd); } } void GUIProfileParser::addControl(const QXmlAttributes& attributes) { /* std::cout << "Control: "; printAttributes(attributes); */ QString id = attributes.value("id"); QString subcontrols = attributes.value("subcontrols"); QString name = attributes.value("name"); QString show = attributes.value("show"); QString background = attributes.value("background"); QString switchtype = attributes.value("switchtype"); QString mandatory = attributes.value("mandatory"); QString split = attributes.value("split"); bool isMandatory = false; if ( !id.isNull() ) { // We need at least an "id". We can set defaults for the rest, if undefined. if ( subcontrols.isNull() || subcontrols.isEmpty() ) { subcontrols = '*'; // for compatibility reasons, we interpret an empty string as match-all (aka "*") } if ( name.isNull() ) { // ignore. isNull() will be checked by all users. } if ( ! mandatory.isNull() && mandatory == "true" ) { isMandatory = true; } if ( !background.isNull() ) { // ignore. isNull() will be checked by all users. } if ( !switchtype.isNull() ) { // ignore. isNull() will be checked by all users. } ProfControl *profControl = new ProfControl(id, subcontrols); - if ( show.isNull() ) { show = '*'; } - profControl->name = name; - profControl->setVisible(GuiVisibility::getByString(show)); + profControl->setName(name); + profControl->setVisibility(show.isNull() ? "all" : show); profControl->setBackgroundColor( background ); profControl->setSwitchtype(switchtype); profControl->setMandatory(isMandatory); - - if ( !split.isNull() && split=="true") { - profControl->setSplit(true); - } - _guiProfile->getControls().push_back(profControl); + if (split=="true") profControl->setSplit(true); + + _guiProfile->addControl(profControl); } // id != null } void GUIProfileParser::printAttributes(const QXmlAttributes& attributes) { if ( attributes.length() > 0 ) { for ( int i = 0 ; i < attributes.length(); i++ ) { std::cout << attributes.qName(i).toUtf8().constData() << ":"<< attributes.value(i).toUtf8().constData() << " , "; } std::cout << std::endl; } } void GUIProfileParser::splitPair(const QString& pairString, std::pair& result, char delim) { int delimPos = pairString.indexOf(delim); if ( delimPos == -1 ) { // delimiter not found => use an empty String for "second" result.first = pairString; result.second = ""; } else { // delimiter found result.first = pairString.mid(0,delimPos); result.second = pairString.left(delimPos+1); } } diff --git a/gui/guiprofile.h b/gui/guiprofile.h index 57ded28f..92b935eb 100644 --- a/gui/guiprofile.h +++ b/gui/guiprofile.h @@ -1,332 +1,234 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 2006-2007 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _GUIPROFILE_H_ #define _GUIPROFILE_H_ class Mixer; #include "kmix_debug.h" #include #include #include #include #include -#include #include -#include #include -struct SortedStringComparator -{ - bool operator()(const std::string&, const std::string&) const; -}; - struct ProfProduct { QString vendor; QString productName; // In case the vendor ships different products under the same productName QString productRelease; QString comment; }; -class ProfControlPrivate -{ -public: - // List of controls, e.g: "rec:1-2,recswitch" - // THIS IS RAW DATA AS LOADED FROM THE PROFILE. DO NOT USE IT, except for debugging. - QString subcontrols; - -}; /** * GuiVisibility can be used in different contexts. One is, to define in the XML GUI Profile, which control to show, e.g. show * "MIC Boost" in EXTENDED mode. The other is for representing the GUI complexity (e.g. for letting the user select a preset like "SIMPLE". */ -class GuiVisibility +enum class GuiVisibility { - enum GuiVisibilityId { SIMPLE, EXTENDED, FULL, CUSTOM, NEVER, DEFAULT }; - QString id; - GuiVisibilityId idCode; - -public: -static GuiVisibility const GuiSIMPLE; -static GuiVisibility const GuiEXTENDED; -static GuiVisibility const GuiFULL; -static GuiVisibility const GuiCUSTOM; -static GuiVisibility const GuiNEVER; // e.g. templates with regexp's -static GuiVisibility const GuiDEFAULT; // default argument to ViewBase::findMdw() - - private: - GuiVisibility(QString id, GuiVisibilityId idCode) - { - this->id = id; - this->idCode = idCode; - } - - public: - QString& getId() - { - return id; - } - - /** - * Returns whether this GuiVisibility satisfies the other GuiVisibility. - * GuiNEVER can never be satisfied - if this or other is GuiNEVER, the result is false. - * GuiCUSTOM is always satisfied - if this or other is GuiCUSTOM, the result is true. - * GuiDEFAULT for the other is always satisfied. - * The other 3 enum values are completely ordered as GuiSIMPLE, GuiEXTENDED, GuiFULL. - *

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

+ * For example, 'Simple' satisfies 'Full', as the simple GUI is part of the full GUI. + */ + bool satisfiesVisibility(GuiVisibility vis) const; private: // The following are the deserialized values of _subcontrols bool _useSubcontrolPlayback; bool _useSubcontrolCapture; bool _useSubcontrolPlaybackSwitch; bool _useSubcontrolCaptureSwitch; bool _useSubcontrolEnum; + QString _id; + QString _name; + // For applying custom colors - QString backgroundColor; + QString _backgroundColor; // For defining the switch type when it is not a standard palyback or capture switch - QString switchtype; + QString _switchtype; // show or hide (contains the GUI type: simple, extended, all) - - GuiVisibility visibility; + GuiVisibility _visibility; bool _mandatory; // A mandatory control must be included in all GUIProfile copies - - ProfControlPrivate *d; bool _split; // true if this widget is to show two sliders + + // List of controls, e.g: "rec:1-2,recswitch" + // THIS IS RAW DATA AS LOADED FROM THE PROFILE. DO NOT USE IT, except for debugging. + QString _subcontrols; }; struct ProductComparator { bool operator()(const ProfProduct*, const ProfProduct*) const; }; + class GUIProfile { public: typedef std::set ProductSet; typedef QList ControlSet; -private: - static QMap& getProfiles() { return s_profiles; } - // Loading - static QString buildProfileName(Mixer* mixer, QString profileName, bool ignoreCard); - static QString buildReadableProfileName(Mixer* mixer, QString profileName); - - static GUIProfile* loadProfileFromXMLfiles(Mixer* mixer, QString profileName); - static void addProfile(GUIProfile* guiprof); - static const QString createNormalizedFilename(const QString& profileId); - - static QMap s_profiles; - - public: GUIProfile(); - virtual ~GUIProfile(); - - static void clearCache(); + ~GUIProfile(); bool readProfile(const QString& ref_fileNamestring); bool finalizeProfile() const; bool writeProfile(); - bool isDirty() const; - void setDirty(); - - void setId(const QString& id); - QString getId() const; - QString getMixerId() const { return _mixerId; } + bool isDirty() const { return (_dirty); } + void setDirty() { _dirty = true; } + void setId(const QString &id) { _id = id; } + QString getId() const { return (_id); } + QString getMixerId() const { return (_mixerId); } - unsigned long match(Mixer* mixer); - friend std::ostream& operator<<(std::ostream& os, const GUIProfile& vol); - friend QTextStream& operator<<(QTextStream &outStream, const GUIProfile& guiprof); - + unsigned long match(Mixer *mixer) const; + friend QTextStream& operator<<(QTextStream &outStream, const GUIProfile& guiprof); - static GUIProfile* find(Mixer* mixer, QString profileName, bool profileNameIsFullyQualified, bool ignoreCardName); - static GUIProfile* find(QString id); - static GUIProfile* selectProfileFromXMLfiles(Mixer*, QString preferredProfile); - static GUIProfile* fallbackProfile(Mixer*); + static void clearCache(); + static GUIProfile *find(const QString &id); + static GUIProfile *find(Mixer *mixer, const QString &profileName, bool profileNameIsFullyQualified, bool ignoreCardName); + static GUIProfile *fallbackProfile(Mixer *mixer); // --- Getters and setters ---------------------------------------------------------------------- - const ControlSet& getControls() const; - ControlSet& getControls(); - void setControls(ControlSet& newControlSet); - - QString getName() const { return _name; } - void setName(QString _name) { this->_name = _name; } + const ControlSet &getControls() const { return (_controls); } - void addProduct(ProfProduct*); + void setControls(ControlSet &newControlSet); + void addControl(ProfControl *ctrl) { _controls.push_back(ctrl); } + void addProduct(ProfProduct *prod) { _products.insert(prod); } + QString getName() const { return (_name); } + void setName(const QString &name) { _name = name; } // --- The values from the tag: No getters and setters for them (yet) ----------------------------- QString _soundcardDriver; // The driver version: 1000*1000*MAJOR + 1000*MINOR + PATCHLEVEL unsigned long _driverVersionMin; unsigned long _driverVersionMax; QString _soundcardName; QString _soundcardType; unsigned long _generation; private: ControlSet _controls; ProductSet _products; QString _id; QString _name; QString _mixerId; bool _dirty; }; -std::ostream& operator<<(std::ostream& os, const GUIProfile& vol); +//std::ostream& operator<<(std::ostream& os, const GUIProfile& vol); QTextStream& operator<<(QTextStream &outStream, const GUIProfile& guiprof); class GUIProfileParser : public QXmlDefaultHandler { public: explicit GUIProfileParser(GUIProfile* ref_gp); // Enumeration for the scope enum ProfileScope { NONE, SOUNDCARD }; bool startDocument() Q_DECL_OVERRIDE; bool startElement( const QString&, const QString&, const QString& , const QXmlAttributes& ) Q_DECL_OVERRIDE; bool endElement( const QString&, const QString&, const QString& ) Q_DECL_OVERRIDE; private: void addControl(const QXmlAttributes& attributes); void addProduct(const QXmlAttributes& attributes); void addSoundcard(const QXmlAttributes& attributes); void addProfileInfo(const QXmlAttributes& attributes); void printAttributes(const QXmlAttributes& attributes); void splitPair(const QString& pairString, std::pair& result, char delim); ProfileScope _scope; GUIProfile* _guiProfile; }; #endif //_GUIPROFILE_H_ diff --git a/gui/viewbase.cpp b/gui/viewbase.cpp index 0dd1819c..d3930da5 100644 --- a/gui/viewbase.cpp +++ b/gui/viewbase.cpp @@ -1,477 +1,481 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "viewbase.h" // Qt #include #include #include // KDE #include #include #include #include #include #include // KMix #include "dialogviewconfiguration.h" #include "gui/guiprofile.h" #include "gui/kmixtoolbox.h" #include "gui/mixdevicewidget.h" #include "gui/mdwslider.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/mixer.h" #include "core/mixertoolbox.h" /** * Creates an empty View. To populate it with MixDevice instances, you must implement * initLayout() in your derived class. */ ViewBase::ViewBase(QWidget* parent, QString id, Qt::WindowFlags f, ViewBase::ViewFlags vflags, QString guiProfileId, KActionCollection *actionColletion) : QWidget(parent, f), _popMenu(NULL), _actions(actionColletion), _vflags(vflags), - guiLevel(GuiVisibility::GuiSIMPLE), + guiLevel(GuiVisibility::Simple), _guiProfileId(guiProfileId) { setObjectName(id); qCDebug(KMIX_LOG) << "id" << id << "flags" << vflags; // When loading the View from the XML profile, guiLevel can get overridden m_viewId = id; if ( _actions == 0 ) { // We create our own action collection, if the actionColletion was 0. // This is currently done for the ViewDockAreaPopup, but only because it has not been converted to use the app-wide // actionCollection(). This is a @todo. _actions = new KActionCollection( this ); } _localActionColletion = new KActionCollection( this ); // Plug in the "showMenubar" action, if the caller wants it. Typically this is only necessary for views in the KMix main window. if ( vflags & ViewBase::HasMenuBar ) { KToggleAction *m = static_cast( _actions->action( name(KStandardAction::ShowMenubar) ) ) ; if ( m != 0 ) { bool visible = ( vflags & ViewBase::MenuBarVisible ); m->setChecked(visible); } } } void ViewBase::addMixer(Mixer *mixer) { _mixers.append(mixer); } QPushButton* ViewBase::createConfigureViewButton() { QPushButton* configureViewButton = new QPushButton(QIcon::fromTheme(QLatin1String("configure")), QString(), this); configureViewButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); configureViewButton->setToolTip(i18n( "Configure this view" )); connect(configureViewButton, SIGNAL(clicked(bool)), SLOT(configureView())); return configureViewButton; } void ViewBase::updateGuiOptions() { setTicks(GlobalConfig::instance().data.showTicks); setLabels(GlobalConfig::instance().data.showLabels); updateMediaPlaybackIcons(); } bool ViewBase::isValid() const { return ( !_mixSet.isEmpty() || isDynamic() ); } void ViewBase::setIcons (bool on) { KMixToolBox::setIcons (_mdws, on ); } void ViewBase::setLabels(bool on) { KMixToolBox::setLabels(_mdws, on ); } void ViewBase::setTicks (bool on) { KMixToolBox::setTicks (_mdws, on ); } /** * Updates all playback icons to their (new) state, e.g. Paused, or Playing */ void ViewBase::updateMediaPlaybackIcons() { for (int i = 0; i < _mdws.count(); ++i) { // Currently media controls are always attached to sliders => use MDWSlider MDWSlider* mdw = qobject_cast(_mdws[i]); if (mdw != 0) { mdw->updateMediaButton(); } } } /** * Create all widgets. * This is a loop over all supported devices of the corresponding view. * On each device add() is called - the derived class must implement add() for creating and placing * the real MixDeviceWidget. * The added MixDeviceWidget is appended to the _mdws list. */ void ViewBase::createDeviceWidgets() { // If this widget is currently visible, it is hidden while the device widgets are // being added and shown again afterwards. This is because, if we are called as // a result of change of orientation or a control being added or removed while we // are visible, isVisibleTo() in ViewSliders::configurationUpdate() appears to // return false even for controls that should be visible. This means that they // do not get included in the label extent calculation and the labels will not // be resized evenly. const bool wasVisible = isVisible(); if (wasVisible) hide(); // hide if currently visible _orientation = orientationSetting(); // refresh orientation from settings qCDebug(KMIX_LOG) << id() << "orientation" << _orientation; initLayout(); foreach (const shared_ptr md, _mixSet) { QWidget *mdw = add(md); // a) Let the implementation do its work _mdws.append(mdw); // b) Add it to the local list connect(mdw, SIGNAL(guiVisibilityChange(MixDeviceWidget*,bool)), SLOT(guiVisibilitySlot(MixDeviceWidget*,bool))); } if (!isDynamic()) { QAction *action = _localActionColletion->addAction("toggle_channels"); action->setText(i18n("Configure Channels...")); connect(action, SIGNAL(triggered(bool)), SLOT(configureView())); } constructionFinished(); // allow view to "polish" itself if (wasVisible) show(); // show again if originally visible } /** * Called when a specific control is to be shown or hidden. At the moment it is only called via * the "hide" action in the MDW context menu. * * @param mdw * @param enable */ void ViewBase::guiVisibilitySlot(MixDeviceWidget* mdw, bool enable) { MixDevice* md = mdw->mixDevice().get(); qCDebug(KMIX_LOG) << "Change " << md->id() << " to visible=" << enable; ProfControl* pctl = findMdw(md->id()); if (pctl == 0) { qCWarning(KMIX_LOG) << "MixDevice not found, and cannot be hidden, id=" << md->id(); return; // Ignore } pctl->setVisible(enable); ControlManager::instance().announce(md->mixer()->id(), ControlManager::ControlList, QString("ViewBase::guiVisibilitySlot")); } // // ---------- Popup stuff START --------------------- void ViewBase::contextMenuEvent(QContextMenuEvent *ev) { showContextMenu(); } /** * Return a popup menu. This contains basic entries. * More can be added by the caller. */ QMenu* ViewBase::getPopup() { popupReset(); return _popMenu; } void ViewBase::popupReset() { QAction *act; delete _popMenu; _popMenu = new QMenu( this ); _popMenu->addSection( QIcon::fromTheme( QLatin1String( "kmix" ) ), i18n("Device Settings" )); act = _localActionColletion->action( "toggle_channels" ); if ( act ) _popMenu->addAction(act); act = _actions->action( "options_show_menubar" ); if ( act ) _popMenu->addAction(act); } /** This will only get executed, when the user has removed all items from the view. Don't remove this method, because then the user cannot get a menu for getting his channels back */ void ViewBase::showContextMenu() { //qCDebug(KMIX_LOG) << "ViewBase::showContextMenu()"; popupReset(); QPoint pos = QCursor::pos(); _popMenu->popup( pos ); } // ---------- Popup stuff END --------------------- void ViewBase::refreshVolumeLevels() { // is virtual } /** * Check all Mixer instances of this view. * If at least on is dynamic, return true. * Please note that usually there is only one Mixer instance per View. * The only exception as of today (June 2011) is the Tray Popup, which * can contain controls from e.g. ALSA and MPRIS2 backends. */ bool ViewBase::isDynamic() const { foreach (Mixer* mixer , _mixers ) { if ( mixer->isDynamic() ) return true; } return false; } bool ViewBase::pulseaudioPresent() const { // We do not use Mixer::pulseaudioPresent(), as we are only interested in Mixer instances contained in this View. foreach (Mixer* mixer , _mixers ) { if ( mixer->getDriverName() == "PulseAudio" ) return true; } return false; } void ViewBase::resetMdws() { // We need to delete the current MixDeviceWidgets so we can recreate them // while (!_mdws.isEmpty()) // delete _mdws.takeFirst(); qDeleteAll(_mdws); _mdws.clear(); // _mixSet contains shared_ptr instances, so clear() should be enough to prevent mem leak _mixSet.clear(); // Clean up our _mixSet so we can reapply our GUIProfile } int ViewBase::visibleControls() const { int visibleCount = 0; foreach (QWidget* qw, _mdws) { if (qw->isVisible()) ++ visibleCount; } return visibleCount; } /** * Open the View configuration dialog. The user can select which channels he wants * to see and which not. */ void ViewBase::configureView() { Q_ASSERT( !isDynamic() ); Q_ASSERT( !pulseaudioPresent() ); DialogViewConfiguration* dvc = new DialogViewConfiguration(0, *this); dvc->show(); } void ViewBase::toggleMenuBarSlot() { //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() start\n"; emit toggleMenuBar(); //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() done\n"; } /** * Loads the configuration of this view. *

* Future directions: The view should probably know its config in advance, so we can use it in #load() and #save() * * * @param config The view for this config */ void ViewBase::load(const KConfig *config) { ViewBase *view = this; QString grp = "View."; grp += view->id(); //KConfigGroup cg = config->group( grp ); qCDebug(KMIX_LOG) << "KMixToolBox::loadView() grp=" << grp.toLatin1(); - static GuiVisibility guiVisibilities[3] = - { GuiVisibility::GuiSIMPLE, GuiVisibility::GuiEXTENDED, GuiVisibility::GuiFULL }; + static const GuiVisibility guiVisibilities[3] = + { + GuiVisibility::Simple, + GuiVisibility::Extended, + GuiVisibility::Full + }; bool guiLevelSet = false; - for (int i=0; i<3; ++i) + for (int i = 0; i<3; ++i) { - GuiVisibility& guiCompl = guiVisibilities[i]; + const GuiVisibility guiCompl = guiVisibilities[i]; bool atLeastOneControlIsShown = false; foreach(QWidget *qmdw, view->_mdws) { MixDeviceWidget *mdw = qobject_cast(qmdw); if (mdw!=nullptr) { shared_ptr md = mdw->mixDevice(); QString devgrp = md->configGroupName(grp); KConfigGroup devcg = config->group(devgrp); if (mdw->inherits("MDWSlider")) { // only sliders have the ability to split apart in multiple channels bool splitChannels = devcg.readEntry("Split", !mdw->isStereoLinked()); mdw->setStereoLinked(!splitChannels); } // Future directions: "Visibility" is very dirty: It is read from either config file or // GUIProfile. Thus we have a lot of doubled mdw visibility code all throughout KMix. bool mdwEnabled = false; // Consult GuiProfile for visibility mdwEnabled = findMdw(mdw->mixDevice()->id(), guiCompl) != 0; // Match GUI complexity // qCWarning(KMIX_LOG) << "---------- FIRST RUN: md=" << md->id() << ", guiVisibility=" << guiCompl.getId() << ", enabled=" << mdwEnabled; if (mdwEnabled) { atLeastOneControlIsShown = true; } mdw->setVisible(mdwEnabled); } // inherits MixDeviceWidget } // for all MDW's if (atLeastOneControlIsShown) { guiLevelSet = true; setGuiLevel(guiCompl); break; // If there were controls in this complexity level, don't try more } } // for try = 0 ... 1 if (!guiLevelSet) - setGuiLevel(guiVisibilities[2]); + setGuiLevel(GuiVisibility::Full); } -void ViewBase::setGuiLevel(GuiVisibility& guiLevel) +void ViewBase::setGuiLevel(GuiVisibility guiLevel) { this->guiLevel = guiLevel; } /** * Checks whether the given mixDevice shall be shown according to the requested * GuiVisibility. All ProfControl objects are inspected. The first found is returned. * * @param mdwId The control ID * @param requestedGuiComplexityName The GUI name * @return The corresponding ProfControl* * */ ProfControl *ViewBase::findMdw(const QString& mdwId, GuiVisibility visibility) const { foreach ( ProfControl* pControl, guiProfile()->getControls() ) { - QRegExp idRegExp(pControl->id); + QRegExp idRegExp(pControl->id()); if ( mdwId.contains(idRegExp) ) { - if (pControl->getVisibility().satisfiesVisibility(visibility)) + if (pControl->satisfiesVisibility(visibility)) { // qCDebug(KMIX_LOG) << " MATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); return pControl; } else { // qCDebug(KMIX_LOG) << "NOMATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); } } } // iterate over all ProfControl entries return (nullptr); // not found } /* * Saves the View configuration */ void ViewBase::save(KConfig *config) const { const QString grp = "View."+id(); // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) bool dynamic = isDynamic(); // TODO 11 Dynamic view configuration for (int i = 0; i<_mdws.count(); ++i) { MixDeviceWidget *mdw = qobject_cast(_mdws[i]); if (mdw!=nullptr) { shared_ptr md = mdw->mixDevice(); //qCDebug(KMIX_LOG) << " grp=" << grp.toLatin1(); //qCDebug(KMIX_LOG) << " mixer=" << view->id().toLatin1(); //qCDebug(KMIX_LOG) << " mdwPK=" << mdw->mixDevice()->id().toLatin1(); QString devgrp = QString("%1.%2.%3").arg(grp, md->mixer()->id(), md->id()); KConfigGroup devcg = config->group(devgrp); if (mdw->inherits("MDWSlider")) { // only sliders have the ability to split apart in multiple channels devcg.writeEntry("Split", !mdw->isStereoLinked()); } /* if (!dynamic) { devcg.writeEntry("Show", mdw->isVisibleTo(view)); // qCDebug(KMIX_LOG) << "Save devgrp" << devgrp << "show=" << mdw->isVisibleTo(view); } */ } // inherits MixDeviceWidget } // for all MDW's if (!dynamic) { // We do not save GUIProfiles (as they cannot be customized) for dynamic mixers (e.g. PulseAudio) if (guiProfile()->isDirty()) { qCDebug(KMIX_LOG) << "Writing dirty profile. grp=" << grp; guiProfile()->writeProfile(); } } } diff --git a/gui/viewbase.h b/gui/viewbase.h index 923d2bca..500f8b42 100644 --- a/gui/viewbase.h +++ b/gui/viewbase.h @@ -1,168 +1,168 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef VIEWBASE_H #define VIEWBASE_H // Qt #include #include #include // KDE #include class QMenu; class QContextMenuEvent; class Mixer; class MixDevice; class MixDeviceWidget; // KMix #include "core/mixdevice.h" #include "core/mixset.h" #include "gui/guiprofile.h" /** * The ViewBase is an abstract base class, to be used for subclassing the real Mixer Views. */ class ViewBase : public QWidget { Q_OBJECT public: enum ViewFlag { HasMenuBar = 0x0001, MenuBarVisible = 0x0002 }; Q_DECLARE_FLAGS(ViewFlags, ViewFlag); ViewBase(QWidget* parent, QString id, Qt::WindowFlags f, ViewFlags vflags, QString guiProfileId, KActionCollection* actionCollection = 0); // The GUI profile will not be removed on destruction, // as it is pooled and might be applied to a new View. virtual ~ViewBase() = default; // This method is called after a configuration update (show/hide controls, split/unsplit). virtual void configurationUpdate() = 0; void load(const KConfig *config); void save(KConfig *config) const; QMenu *getPopup(); bool isDynamic() const; bool pulseaudioPresent() const; int visibleControls() const; void setIcons(bool on); void setLabels(bool on); void setTicks(bool on); bool isValid() const; QString id() const { return (m_viewId); } GUIProfile* guiProfile() const { return (GUIProfile::find(_guiProfileId)); } - ProfControl *findMdw(const QString &mdwId, GuiVisibility visibility = GuiVisibility::GuiDEFAULT) const; + ProfControl *findMdw(const QString &mdwId, GuiVisibility visibility = GuiVisibility::Default) const; KActionCollection *actionCollection() const { return (_actions); }; const QList &getMixers() const { return (_mixers); }; int mixDeviceCount() const { return (_mdws.count()); } QWidget *mixDeviceAt(int i) const { return (_mdws.at(i)); } Qt::Orientation orientation() const { return (_orientation); } private: /** * Contains the widgets for the _mixSet. There is a 1:1 relationship, which means: * _mdws[i] is the Widget for the MixDevice _mixSet[i] - please see ViewBase::createDeviceWidgets(). * Hint: !! The new ViewSurround class shows that a 1:1 relationship does not work in a general scenario. * I actually DID expect this. The solution is unclear yet, probably there will be a virtual mapper method. */ QList _mdws; QMenu *_popMenu; KActionCollection* _actions; // -<- application wide action collection KActionCollection *_localActionColletion; ViewFlags _vflags; Qt::Orientation _orientation; GuiVisibility guiLevel; const QString _guiProfileId; QString m_viewId; private: - void setGuiLevel(GuiVisibility& guiLevel); + void setGuiLevel(GuiVisibility guiLevel); void updateMediaPlaybackIcons(); void popupReset(); protected: MixSet _mixSet; QList _mixers; protected: void resetMdws(); void updateGuiOptions(); QPushButton *createConfigureViewButton(); void addMixer(Mixer *mixer); virtual void initLayout() = 0; virtual Qt::Orientation orientationSetting() const = 0; /** * Creates the widgets for all supported devices. The default implementation loops * over the supported MixDevice's and calls add() for each of it. */ void createDeviceWidgets(); /** * Popup stuff */ void contextMenuEvent(QContextMenuEvent *ev) Q_DECL_OVERRIDE; virtual void showContextMenu(); // Creates a suitable representation for the given MixDevice. virtual QWidget *add(const shared_ptr) = 0; // This method is called by ViewBase at the end of createDeviceWidgets(). The default // implementation does nothing. Subclasses can override this method for doing final // touches. This is very much like polish(), but called at an exactly well-known time. // Also I do not want Views to interfere with polish() virtual void constructionFinished() = 0; public slots: virtual void refreshVolumeLevels(); // TODO remove virtual void configureView(); signals: void toggleMenuBar(); private slots: void guiVisibilitySlot(MixDeviceWidget* source, bool enable); void toggleMenuBarSlot(); }; Q_DECLARE_OPERATORS_FOR_FLAGS(ViewBase::ViewFlags); #endif diff --git a/gui/viewsliders.cpp b/gui/viewsliders.cpp index 902f9ed3..c2eae30d 100644 --- a/gui/viewsliders.cpp +++ b/gui/viewsliders.cpp @@ -1,418 +1,418 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //#define TEST_MIXDEVICE_COMPOSITE #undef TEST_MIXDEVICE_COMPOSITE #ifdef TEST_MIXDEVICE_COMPOSITE #ifdef __GNUC__ #warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #warning !!! MIXDEVICE COMPOSITE TESTING IS ACTIVATED !!! #warning !!! THIS IS PRE-ALPHA CODE! !!! #warning !!! DO NOT SHIP KMIX IN THIS STATE !!! #warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #endif #endif #include "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, 0, this); m_layoutSwitches->addWidget(mdw); } else // control is a slider { mdw = new MDWSlider(md, MixDeviceWidget::ShowMute|MixDeviceWidget::ShowCapture, this); 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); + 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; + const bool subcontrolWanted = subcontrolPlaybackWanted || subcontrolCaptureWanted || subcontrolEnumWanted; if (!subcontrolWanted) continue; md->setControlProfile(control); - if (!control->name.isNull()) + 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); + 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 the view layout by setting the extent of all the control labels // to allow space for the largest. The extent of a control's label is // found from its labelExtentHint() virtual function (which takes account // of the control layout direction), and is then set by its setLabelExtent() // virtual function. // The maximum extent is calculated and set separately for sliders and // for switches (enums). int labelExtentSliders = 0; int labelExtentSwitches = 0; const int num = mixDeviceCount(); // Pass 1: Set the visibility of all controls for (int i = 0; i(mixDeviceAt(i)); if (mdw==nullptr) continue; // The GUI level has been set earlier, by inspecting the controls ProfControl *matchingControl = findMdw(mdw->mixDevice()->id()); mdw->setVisible(matchingControl!=nullptr); } // Pass 2: Find the maximum extent of all applicable controls for (int i = 0; iisVisibleTo(this)) continue; // not currently visible 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()); }