diff --git a/gui/dialogviewconfiguration.cpp b/gui/dialogviewconfiguration.cpp index 72563d99..33888ae7 100644 --- a/gui/dialogviewconfiguration.cpp +++ b/gui/dialogviewconfiguration.cpp @@ -1,457 +1,455 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dialogviewconfiguration.h" #include #include #include #include #include #include #include #include "gui/guiprofile.h" #include "gui/mixdevicewidget.h" #include "core/ControlManager.h" #include "core/mixdevice.h" #include "core/mixer.h" DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent) : QListWidgetItem(parent) { qCDebug(KMIX_LOG) << "DialogViewConfigurationItem() default constructor"; refreshItem(); } DialogViewConfigurationItem::DialogViewConfigurationItem(QListWidget *parent, QString id, bool shown, QString name, int splitted, const QString& iconName) : QListWidgetItem(parent), _id(id), _shown(shown), _name(name), _splitted(splitted), _iconName(iconName) { refreshItem(); } void DialogViewConfigurationItem::refreshItem() { setFlags((flags() | Qt::ItemIsDragEnabled) & ~Qt::ItemIsDropEnabled); setText(_name); setIcon(KIconLoader::global()->loadIcon( _iconName, KIconLoader::Small, IconSize(KIconLoader::Toolbar) )); setData(Qt::ToolTipRole, _id); // a hack. I am giving up to do it right setData(Qt::DisplayRole, _name); } /** * Serializer. Used for DnD. */ static QDataStream & operator<< ( QDataStream & s, const DialogViewConfigurationItem & item ) { s << item._id; s << item._shown; s << item._name; s << item._splitted; s << item._iconName; //qCDebug(KMIX_LOG) << "<< serialize << " << s; return s; } /** * Deserializer. Used for DnD. */ static QDataStream & operator>> ( QDataStream & s, DialogViewConfigurationItem & item ) { QString id; s >> id; item._id = id; bool shown; s >> shown; item._shown = shown; QString name; s >> name; item._name = name; int splitted; s >> splitted; item._splitted = splitted; QString iconName; s >> iconName; item._iconName = iconName; //qCDebug(KMIX_LOG) << ">> deserialize >> " << id << name << iconName; return s; } DialogViewConfigurationWidget::DialogViewConfigurationWidget(QWidget *parent) : QListWidget(parent), m_activeList(true) { setDragDropMode(QAbstractItemView::DragDrop); setDropIndicatorShown(true); setAcceptDrops(true); setSelectionMode(QAbstractItemView::SingleSelection); setDragEnabled(true); viewport()->setAcceptDrops(true); setAlternatingRowColors(true); } QMimeData* DialogViewConfigurationWidget::mimeData(const QList items) const { if (items.isEmpty()) return 0; QMimeData* mimedata = new QMimeData(); DialogViewConfigurationItem* item = 0; QByteArray data; { QDataStream stream(&data, QIODevice::WriteOnly); // we only support single selection item = static_cast(items.first()); stream << *item; } bool active = isActiveList(); mimedata->setData("application/x-kde-action-list", data); mimedata->setData("application/x-kde-source-treewidget", active ? "active" : "inactive"); return mimedata; } bool DialogViewConfigurationWidget::dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction /*action*/) { const QByteArray data = mimeData->data("application/x-kde-action-list"); if (data.isEmpty()) return false; QDataStream stream(data); const bool sourceIsActiveList = mimeData->data("application/x-kde-source-treewidget") == "active"; DialogViewConfigurationItem* item = new DialogViewConfigurationItem(0); // needs parent, use this temporarily stream >> *item; item->refreshItem(); emit dropped(this, index, item, sourceIsActiveList); return true; } DialogViewConfiguration::DialogViewConfiguration(QWidget *parent, ViewBase &view) : DialogBase(parent), _view(view) { setWindowTitle( i18n( "Configure Channels" ) ); setButtons( QDialogButtonBox::Ok|QDialogButtonBox::Cancel ); QWidget *frame = new QWidget( this ); frame->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); setMainWidget( frame ); // The _layout will hold two items: The title and the Drag-n-Drop area QVBoxLayout *layout = new QVBoxLayout(frame); // --- HEADER --- QLabel *qlb = new QLabel( i18n("Configure the visible channels. Drag icons between the lists to update."), frame ); layout->addWidget(qlb); _glayout = new QGridLayout(); _glayout->setMargin(0); layout->addLayout(_glayout); _qlw = 0; _qlwInactive = 0; createPage(); } /** * Drop an item from one list to the other */ void DialogViewConfiguration::slotDropped ( DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList ) { //qCDebug(KMIX_LOG) << "dropped item (index" << index << "): " << item->_id << item->_shown << item->_name << item->_splitted << item->_iconName; if ( list == _qlw ) { //DialogViewConfigurationItem* after = index > 0 ? static_cast(list->item(index-1)) : 0; //qCDebug(KMIX_LOG) << "after" << after->text() << after->internalTag(); if ( sourceIsActiveList ) { // has been dragged within the active list (moved). _qlw->insertItem ( index, item ); //moveActive(item, after); } else { // dragged from the inactive list to the active list _qlw->insertItem ( index, item ); //insertActive(item, after, true); } } else if ( list == _qlwInactive ) { // has been dragged to the inactive list -> remove from the active list. //removeActive(item); _qlwInactive->insertItem ( index, item ); } } void DialogViewConfiguration::addSpacer(int row, int col) { QWidget *dummy = new QWidget(); dummy->setFixedWidth(4); _glayout->addWidget(dummy,row,col); } void DialogViewConfiguration::moveSelection(DialogViewConfigurationWidget* from, DialogViewConfigurationWidget* to) { foreach ( QListWidgetItem* item, from->selectedItems() ) { QListWidgetItem *clonedItem = item->clone(); to->addItem ( clonedItem ); to->setCurrentItem(clonedItem); delete item; } } void DialogViewConfiguration::moveSelectionToActiveList() { moveSelection(_qlwInactive, _qlw); } void DialogViewConfiguration::moveSelectionToInactiveList() { moveSelection(_qlw, _qlwInactive); } void DialogViewConfiguration::selectionChangedActive() { // bool activeIsNotEmpty = _qlw->selectedItems().isEmpty(); moveRightButton->setEnabled(! _qlw->selectedItems().isEmpty()); moveLeftButton->setEnabled(false); } void DialogViewConfiguration::selectionChangedInactive() { moveLeftButton->setEnabled(! _qlwInactive->selectedItems().isEmpty()); moveRightButton->setEnabled(false); } /** * Create basic widgets of the Dialog. */ void DialogViewConfiguration::createPage() { - QList &mdws = _view._mdws; - QLabel *l1 = new QLabel( i18n("Visible channels:") ); _glayout->addWidget(l1,0,0); QLabel *l2 = new QLabel( i18n("Available channels:") ); _glayout->addWidget(l2,0,6); QWidget *frame = mainWidget(); _qlwInactive = new DialogViewConfigurationWidget(frame); _qlwInactive->setDragDropMode(QAbstractItemView::DragDrop); _qlwInactive->setActiveList(false); _glayout->addWidget(_qlwInactive,1,6); connect(_qlwInactive, SIGNAL(dropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool)), this , SLOT(slotDropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool))); addSpacer(1,1); const QIcon& icon = QIcon::fromTheme( QLatin1String( "arrow-left" )); moveLeftButton = new QPushButton(icon, ""); moveLeftButton->setEnabled(false); moveLeftButton->setToolTip(i18n("Move the selected channel to the visible list")); _glayout->addWidget(moveLeftButton,1,2); connect(moveLeftButton, SIGNAL(clicked(bool)), SLOT(moveSelectionToActiveList())); addSpacer(1,3); const QIcon& icon2 = QIcon::fromTheme( QLatin1String( "arrow-right" )); moveRightButton = new QPushButton(icon2, ""); moveRightButton->setEnabled(false); moveRightButton->setToolTip(i18n("Move the selected channel to the available (hidden) list")); _glayout->addWidget(moveRightButton,1,4); connect(moveRightButton, SIGNAL(clicked(bool)), SLOT(moveSelectionToInactiveList())); addSpacer(1,5); _qlw = new DialogViewConfigurationWidget(frame); _glayout->addWidget(_qlw,1,0); connect(_qlw , SIGNAL(dropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool)), this , SLOT(slotDropped(DialogViewConfigurationWidget*,int,DialogViewConfigurationItem*,bool))); // --- CONTROLS IN THE GRID ------------------------------------ //QPalette::ColorRole bgRole; - for ( int i=0; i(_view.mixDeviceAt(i)); + if (mdw==nullptr) continue; + //if ( i%2 == 0) bgRole = QPalette::Base; else bgRole = QPalette::AlternateBase; - QWidget *qw = mdws[i]; - if ( qw->inherits("MixDeviceWidget") ) { - MixDeviceWidget *mdw = static_cast(qw); shared_ptr md = mdw->mixDevice(); - QString mdName = md->readableName(); + const QString mdName = md->readableName(); int splitted = -1; if ( ! md->isEnum() ) { splitted = ( md->playbackVolume().count() > 1) || ( md->captureVolume().count() > 1 ) ; } //qCDebug(KMIX_LOG) << "add DialogViewConfigurationItem: " << mdName << " visible=" << mdw->isVisible() << "splitted=" << splitted; if ( mdw->isVisible() ) { new DialogViewConfigurationItem(_qlw, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); } else { new DialogViewConfigurationItem(_qlwInactive, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); } /* if ( ! md->isEnum() && ( ( md->playbackVolume().count() > 1) || ( md->captureVolume().count() > 1) ) ) { cb = new QCheckBox( "", vboxForScrollView ); // split cb->setBackgroundRole(bgRole); cb->setAutoFillBackground(true); _qSplitCB.append(cb); cb->setChecked( ! mdw->isStereoLinked() ); grid->addWidget(cb,1+i,1); } else { _qSplitCB.append(0); } */ /* if ( ! md->isEnum() && ( md->playbackVolume().count() + md->captureVolume().count() >0 ) ) { cb = new QCheckBox( "", vboxForScrollView ); // limit cb->setBackgroundRole(bgRole); cb->setAutoFillBackground(true); _qLimitCB.append(cb); grid->addWidget(cb,1+i,2); } else { */ //_qLimitCB.append(0); /*}*/ - } // is not enum } // for all MDW's connect(_qlwInactive, SIGNAL(itemSelectionChanged()), this , SLOT(selectionChangedInactive())); connect(_qlw, SIGNAL(itemSelectionChanged()), this , SLOT(selectionChangedActive())); // scrollArea->updateGeometry(); updateGeometry(); connect(this, SIGNAL(accepted()), this, SLOT(apply())); #ifndef QT_NO_ACCESSIBILITY moveLeftButton->setAccessibleName( i18n("Show the selected channel") ); moveRightButton->setAccessibleName( i18n("Hide the selected channel") ); _qlw->setAccessibleName( i18n("Visible channels") ); _qlwInactive->setAccessibleName( i18n("Available channels") ); #endif } DialogViewConfiguration::~DialogViewConfiguration() { } void DialogViewConfiguration::apply() { // --- We have a 3-Step Apply of the Changes ------------------------------- // -1- Update view and profile ***************************************** GUIProfile* prof = _view.guiProfile(); GUIProfile::ControlSet& oldControlset = prof->getControls(); 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); 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) { - int numRows = model->rowCount(); + const int numRows = model->rowCount(); + const int num = _view.mixDeviceCount(); + for (int row = 0; row < numRows; ++row) { // -1- Extract the value from the model *************************** QModelIndex index = model->index(row, 0); QVariant vdci; vdci = model->data(index, Qt::ToolTipRole); // TooltipRole stores the ID (well, thats not really clean design, but it works) QString ctlId = vdci.toString(); - // -2- Find the mdw, und update it ************************** - foreach ( QWidget *qw, _view._mdws ) + for (int i = 0; i(qw); - if ( !mdw ) { - continue; - } + MixDeviceWidget *mdw = qobject_cast(_view.mixDeviceAt(i)); + if (mdw==nullptr) continue; if ( mdw->mixDevice()->id() == ctlId ) { mdw->setVisible(isActiveView); break; } // mdw was found } // find mdw // -3- Insert it in the new ControlSet ************************** // qCDebug(KMIX_LOG) << "Should add to new ControlSet: " << ctlId; foreach ( ProfControl* control, oldCtlSet) { //qCDebug(KMIX_LOG) << " checking " << control->id; QRegExp idRegexp(control->id); 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 // 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/viewbase.cpp b/gui/viewbase.cpp index c89dd546..73958920 100644 --- a/gui/viewbase.cpp +++ b/gui/viewbase.cpp @@ -1,508 +1,476 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "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), _guiProfileId(guiProfileId) -, guiLevel(GuiVisibility::GuiSIMPLE) + : QWidget(parent, f), + _popMenu(NULL), + _actions(actionColletion), + _vflags(vflags), + guiLevel(GuiVisibility::GuiSIMPLE), + _guiProfileId(guiProfileId) { setObjectName(id); // When loading the View from the XML profile, guiLevel can get overridden m_viewId = id; - // TODO: does not need to be a member - configureIcon = QIcon::fromTheme( QLatin1String( "configure" )); - if ( _actions == 0 ) { // We create our own action collection, if the actionColletion was 0. // This is currently done for the ViewDockAreaPopup, but only because it has not been converted to use the app-wide // actionCollection(). This is a @todo. _actions = new KActionCollection( this ); } _localActionColletion = new KActionCollection( this ); // Plug in the "showMenubar" action, if the caller wants it. Typically this is only necessary for views in the KMix main window. if ( vflags & ViewBase::HasMenuBar ) { KToggleAction *m = static_cast( _actions->action( name(KStandardAction::ShowMenubar) ) ) ; if ( m != 0 ) { bool visible = ( vflags & ViewBase::MenuBarVisible ); m->setChecked(visible); } } } -ViewBase::~ViewBase() -{ - // Hint: The GUI profile will not be removed, as it is pooled and might be applied to a new View. -} - void ViewBase::addMixer(Mixer *mixer) { _mixers.append(mixer); } QPushButton* ViewBase::createConfigureViewButton() { - QPushButton* configureViewButton = new QPushButton(configureIcon, "", this); + QPushButton* configureViewButton = new QPushButton(QIcon::fromTheme(QLatin1String("configure")), + QString(), this); configureViewButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); configureViewButton->setToolTip(i18n( "Configure this view" )); connect(configureViewButton, SIGNAL(clicked(bool)), SLOT(configureView())); return configureViewButton; } void ViewBase::updateGuiOptions() { setTicks(GlobalConfig::instance().data.showTicks); setLabels(GlobalConfig::instance().data.showLabels); updateMediaPlaybackIcons(); } -QString ViewBase::id() const { - return m_viewId; -} - bool ViewBase::isValid() const { return ( !_mixSet.isEmpty() || isDynamic() ); } void ViewBase::setIcons (bool on) { KMixToolBox::setIcons (_mdws, on ); } void ViewBase::setLabels(bool on) { KMixToolBox::setLabels(_mdws, on ); } void ViewBase::setTicks (bool on) { KMixToolBox::setTicks (_mdws, on ); } /** * Updates all playback icons to their (new) state, e.g. Paused, or Playing */ void ViewBase::updateMediaPlaybackIcons() { for (int i = 0; i < _mdws.count(); ++i) { // Currently media controls are always attached to sliders => use MDWSlider MDWSlider* mdw = qobject_cast(_mdws[i]); if (mdw != 0) { mdw->updateMediaButton(); } } } /** * Create all widgets. * This is a loop over all supported devices of the corresponding view. * On each device add() is called - the derived class must implement add() for creating and placing * the real MixDeviceWidget. * The added MixDeviceWidget is appended to the _mdws list. */ void ViewBase::createDeviceWidgets() { // 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 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::mousePressEvent( QMouseEvent *e ) +// // ---------- Popup stuff START --------------------- + +void ViewBase::contextMenuEvent(QContextMenuEvent *ev) { - if ( e->button() == Qt::RightButton ) - showContextMenu(); + showContextMenu(); } /** * Return a popup menu. This contains basic entries. * More can be added by the caller. */ QMenu* ViewBase::getPopup() { popupReset(); return _popMenu; } void ViewBase::popupReset() { QAction *act; delete _popMenu; _popMenu = new QMenu( this ); _popMenu->addSection( QIcon::fromTheme( QLatin1String( "kmix" ) ), i18n("Device Settings" )); act = _localActionColletion->action( "toggle_channels" ); if ( act ) _popMenu->addAction(act); act = _actions->action( "options_show_menubar" ); if ( act ) _popMenu->addAction(act); } /** This will only get executed, when the user has removed all items from the view. Don't remove this method, because then the user cannot get a menu for getting his channels back */ void ViewBase::showContextMenu() { //qCDebug(KMIX_LOG) << "ViewBase::showContextMenu()"; popupReset(); QPoint pos = QCursor::pos(); _popMenu->popup( pos ); } 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() +int ViewBase::visibleControls() const { int visibleCount = 0; foreach (QWidget* qw, _mdws) { if (qw->isVisible()) ++ visibleCount; } return visibleCount; } /** * Open the View configuration dialog. The user can select which channels he wants * to see and which not. */ void ViewBase::configureView() { Q_ASSERT( !isDynamic() ); Q_ASSERT( !pulseaudioPresent() ); DialogViewConfiguration* dvc = new DialogViewConfiguration(0, *this); dvc->show(); } void ViewBase::toggleMenuBarSlot() { //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() start\n"; emit toggleMenuBar(); //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() done\n"; } /** * Loads the configuration of this view. *

* Future directions: The view should probably know its config in advance, so we can use it in #load() and #save() * * * @param config The view for this config */ -void ViewBase::load(KConfig *config) +void ViewBase::load(const KConfig *config) { ViewBase *view = this; QString grp = "View."; grp += view->id(); //KConfigGroup cg = config->group( grp ); qCDebug(KMIX_LOG) << "KMixToolBox::loadView() grp=" << grp.toLatin1(); static GuiVisibility guiVisibilities[3] = { GuiVisibility::GuiSIMPLE, GuiVisibility::GuiEXTENDED, GuiVisibility::GuiFULL }; bool guiLevelSet = false; for (int i=0; i<3; ++i) { 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]); } void ViewBase::setGuiLevel(GuiVisibility& guiLevel) { this->guiLevel = guiLevel; } /** * Checks whether the given mixDevice shall be shown according to the requested * GuiVisibility. All ProfControl objects are inspected. The first found is returned. * * @param mdwId The control ID * @param requestedGuiComplexityName The GUI name * @return The corresponding ProfControl* * */ -ProfControl* ViewBase::findMdw(const QString& mdwId, GuiVisibility visibility) +ProfControl *ViewBase::findMdw(const QString& mdwId, GuiVisibility visibility) const { foreach ( ProfControl* pControl, guiProfile()->getControls() ) { QRegExp idRegExp(pControl->id); if ( mdwId.contains(idRegExp) ) { - if (pControl->getVisibility().satisfiesVisibility(visibility)) + if (visibility==GuiVisibility::GuiSIMPLE || + pControl->getVisibility().satisfiesVisibility(visibility)) { // qCDebug(KMIX_LOG) << " MATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); return pControl; } else { // qCDebug(KMIX_LOG) << "NOMATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); } } - } // iterate over all ProfControl entries - - return 0; // not found + } // iterate over all ProfControl entries + return (nullptr); // not found } -/** - * Returns the ProfControl* to the given id. The first found is returned. - * GuiVisibilityis not taken into account. . All ProfControl objects are inspected. - * - * @param id The control ID - * @return The corresponding ProfControl*, or 0 if no match was found - */ -ProfControl* ViewBase::findMdw(const QString& id) -{ - foreach ( ProfControl* pControl, guiProfile()->getControls() ) - { - QRegExp idRegExp(pControl->id); - //qCDebug(KMIX_LOG) << "KMixToolBox::loadView() try match " << (*pControl).id << " for " << mdw->mixDevice()->id(); - if ( id.contains(idRegExp) ) - { - return pControl; - } - } // iterate over all ProfControl entries - - return 0;// not found -} - /* * Saves the View configuration */ -void ViewBase::save(KConfig *config) +void ViewBase::save(KConfig *config) const { - ViewBase *view = this; - QString grp = "View."; - grp += view->id(); + const QString grp = "View."+id(); // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) bool dynamic = isDynamic(); // TODO 11 Dynamic view configuration - for (int i = 0; i < view->_mdws.count(); ++i) + for (int i = 0; i<_mdws.count(); ++i) { - QWidget *qmdw = view->_mdws[i]; - MixDeviceWidget *mdw = qobject_cast(qmdw); + MixDeviceWidget *mdw = qobject_cast(_mdws[i]); if (mdw!=nullptr) { shared_ptr md = mdw->mixDevice(); //qCDebug(KMIX_LOG) << " grp=" << grp.toLatin1(); //qCDebug(KMIX_LOG) << " mixer=" << view->id().toLatin1(); //qCDebug(KMIX_LOG) << " mdwPK=" << mdw->mixDevice()->id().toLatin1(); QString devgrp = QString("%1.%2.%3").arg(grp, md->mixer()->id(), md->id()); KConfigGroup devcg = config->group(devgrp); if (mdw->inherits("MDWSlider")) { // only sliders have the ability to split apart in multiple channels devcg.writeEntry("Split", !mdw->isStereoLinked()); } /* if (!dynamic) { devcg.writeEntry("Show", mdw->isVisibleTo(view)); // qCDebug(KMIX_LOG) << "Save devgrp" << devgrp << "show=" << mdw->isVisibleTo(view); } */ } // inherits MixDeviceWidget } // for all MDW's if (!dynamic) { // We do not save GUIProfiles (as they cannot be customized) for dynamic mixers (e.g. PulseAudio) if (guiProfile()->isDirty()) { qCDebug(KMIX_LOG) << "Writing dirty profile. grp=" << grp; guiProfile()->writeProfile(); } } } // ---------- Popup stuff END --------------------- diff --git a/gui/viewbase.h b/gui/viewbase.h index 4d53bbf8..fc83217b 100644 --- a/gui/viewbase.h +++ b/gui/viewbase.h @@ -1,171 +1,166 @@ //-*-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 #include +#include // KDE #include + class QMenu; +class QContextMenuEvent; class Mixer; class MixDevice; // KMix #include "core/mixdevice.h" #include "core/mixset.h" #include "gui/guiprofile.h" #include "gui/mixdevicewidget.h" /** - * The ViewBase is a virtual base class, to be used for subclassing the real Mixer Views. + * The ViewBase is an abstract base class, to be used for subclassing the real Mixer Views. */ class ViewBase : public QWidget { Q_OBJECT -friend class KMixToolBox; // the toolbox is everybodys friend :-) - public: + enum ViewFlag + { + HasMenuBar = 0x0001, + MenuBarVisible = 0x0002, + Horizontal = 0x0004, + Vertical = 0x0008 + }; + Q_DECLARE_FLAGS(ViewFlags, ViewFlag); - typedef uint ViewFlags; - enum ViewFlagsEnum { - // Regular flags - HasMenuBar = 0x0001, - MenuBarVisible = 0x0002, - Horizontal = 0x0004, - Vertical = 0x0008 - }; - ViewBase(QWidget* parent, QString id, Qt::WindowFlags f, ViewFlags vflags, QString guiProfileId, KActionCollection* actionCollection = 0); - virtual ~ViewBase(); - - void addMixer(Mixer *mixer); - - QString id() const; - - // This method is called by ViewBase at the end of createDeviceWidgets(). The default - // implementation does nothing. Subclasses can override this method for doing final - // touches. This is very much like polish(), but called at an exactly well-known time. - // Also I do not want Views to interfere with polish() - virtual void constructionFinished() = 0; - - /** - * Creates a suitable representation for the given MixDevice. - */ - virtual QWidget* add(const shared_ptr) = 0; + // The GUI profile will not be removed on destruction, + // as it is pooled and might be applied to a new View. + virtual ~ViewBase() = default; // This method is called after a configuration update (show/hide controls, split/unsplit). virtual void configurationUpdate() = 0; - void load(KConfig *config); - void save(KConfig *config); + void load(const KConfig *config); + void save(KConfig *config) const; /** * Creates the widgets for all supported devices. The default implementation loops * over the supported MixDevice's and calls add() for each of it. */ virtual void createDeviceWidgets(); - int visibleControls(); - + QMenu *getPopup(); + bool isDynamic() const; bool pulseaudioPresent() const; - - /** - * Popup stuff - */ - virtual QMenu* getPopup(); - virtual void popupReset(); - virtual void showContextMenu(); - - virtual bool isValid() const; + int visibleControls() const; void setIcons(bool on); void setLabels(bool on); void setTicks(bool on); - GUIProfile* guiProfile() { return GUIProfile::find(_guiProfileId); }; - // GUIComplexity getGuiComplexity() { return guiComplexity; }; - ProfControl* findMdw(const QString& id); - ProfControl* findMdw(const QString& mdwId, GuiVisibility visibility); + bool isValid() const; + QString id() const { return (m_viewId); } - - KActionCollection* actionCollection() { return _actions; }; + GUIProfile* guiProfile() const { return (GUIProfile::find(_guiProfileId)); } + + ProfControl *findMdw(const QString &mdwId, GuiVisibility visibility = GuiVisibility::GuiSIMPLE) const; + + KActionCollection *actionCollection() const { return (_actions); }; + const QList &getMixers() const { return (_mixers); }; - QList& getMixers() { return _mixers; }; + int mixDeviceCount() const { return (_mdws.count()); } + QWidget *mixDeviceAt(int i) const { return (_mdws.at(i)); } +private: /** * Contains the widgets for the _mixSet. There is a 1:1 relationship, which means: * _mdws[i] is the Widget for the MixDevice _mixSet[i] - please see ViewBase::createDeviceWidgets(). * Hint: !! The new ViewSurround class shows that a 1:1 relationship does not work in a general scenario. * I actually DID expect this. The solution is unclear yet, probably there will be a virtual mapper method. */ QList _mdws; -protected: - MixSet _mixSet; - QList _mixers; QMenu *_popMenu; KActionCollection* _actions; // -<- application wide action collection + KActionCollection *_localActionColletion; ViewFlags _vflags; + GuiVisibility guiLevel; const QString _guiProfileId; - KActionCollection *_localActionColletion; - QIcon configureIcon; + QString m_viewId; - virtual void initLayout() = 0; +private: + void setGuiLevel(GuiVisibility& guiLevel); + void updateMediaPlaybackIcons(); + void popupReset(); + +protected: + MixSet _mixSet; + QList _mixers; + +protected: void resetMdws(); void updateGuiOptions(); - QPushButton* createConfigureViewButton(); + QPushButton *createConfigureViewButton(); + void addMixer(Mixer *mixer); - void setGuiLevel(GuiVisibility& guiLevel); + virtual void initLayout() = 0; - GuiVisibility guiLevel; + /** + * Popup stuff + */ + void contextMenuEvent(QContextMenuEvent *ev) Q_DECL_OVERRIDE; + virtual void showContextMenu(); + + // Creates a suitable representation for the given MixDevice. + virtual QWidget *add(const shared_ptr) = 0; + + // This method is called by ViewBase at the end of createDeviceWidgets(). The default + // implementation does nothing. Subclasses can override this method for doing final + // touches. This is very much like polish(), but called at an exactly well-known time. + // Also I do not want Views to interfere with polish() + virtual void constructionFinished() = 0; public slots: virtual void refreshVolumeLevels(); // TODO remove virtual void configureView(); - void toggleMenuBarSlot(); - -protected slots: - void mousePressEvent( QMouseEvent *e ) Q_DECL_OVERRIDE; signals: void toggleMenuBar(); -private: - QString m_viewId; - void updateMediaPlaybackIcons(); - private slots: void guiVisibilitySlot(MixDeviceWidget* source, bool enable); - + void toggleMenuBarSlot(); }; -#endif +Q_DECLARE_OPERATORS_FOR_FLAGS(ViewBase::ViewFlags); +#endif diff --git a/gui/viewdockareapopup.cpp b/gui/viewdockareapopup.cpp index 01c3fdaf..22e00eb9 100644 --- a/gui/viewdockareapopup.cpp +++ b/gui/viewdockareapopup.cpp @@ -1,446 +1,441 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gui/viewdockareapopup.h" // Qt #include #include #include #include #include #include #include // KDE #include #include // KMix #include "apps/kmix.h" #include "core/mixer.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "gui/dialogchoosebackends.h" #include "gui/guiprofile.h" #include "gui/kmixprefdlg.h" #include "gui/mdwslider.h" #include "dialogbase.h" // Restore volume button feature is incomplete => disabling for KDE 4.10 #undef RESTORE_VOLUME_BUTTON QString ViewDockAreaPopup::InternedString_Star = QString("*"); QString ViewDockAreaPopup::InternedString_Subcontrols = QString("pvolume,cvolume,pswitch,cswitch"); ProfControl* ViewDockAreaPopup::MatchAllForSoundMenu = 0; //ProfControl(ViewDockAreaPopup::InternedString_Star, ViewDockAreaPopup::InternedString_Subcontrols); ViewDockAreaPopup::ViewDockAreaPopup(QWidget* parent, QString id, ViewBase::ViewFlags vflags, QString guiProfileId, KMixWindow *dockW) : ViewBase(parent, id, 0, vflags, guiProfileId), _kmixMainWindow(dockW) { resetRefs(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); /* * Bug 206724: * Following are excerpts of trying to receive key events while this popup is open. * The best I could do with lots of hacks is to get the keyboard events after a mouse-click in the popup. * But such a solution is neither intuitive nor helpful - if one clicks, then usage of keyboard makes not much sense any longer. * I finally gave up on fixing Bug 206724. */ /* releaseKeyboard(); setFocusPolicy(Qt::StrongFocus); setFocus(Qt::TabFocusReason); releaseKeyboard(); mainWindowButton->setFocusPolicy(Qt::StrongFocus); Also implemented the following "event handlers", but they do not show any signs of key presses: keyPressEvent() x11Event() eventFilter() */ foreach ( Mixer* mixer, Mixer::mixers() ) { // Adding all mixers, as we potentially want to show all master controls addMixer(mixer); // The list will be redone in initLayout() with the actual Mixer instances to use } restoreVolumeIcon = QIcon::fromTheme(QLatin1String("quickopen-file")); createDeviceWidgets(); // Register listeners for all mixers ControlManager::instance().addListener( QString(), // all mixers ControlManager::GUI|ControlManager::ControlList|ControlManager::Volume|ControlManager::MasterChanged, this, QString("ViewDockAreaPopup")); } ViewDockAreaPopup::~ViewDockAreaPopup() { ControlManager::instance().removeListener(this); delete _layoutMDW; // Hint: optionsLayout and "everything else" is deleted when "delete _layoutMDW" cascades down } void ViewDockAreaPopup::controlsChange(ControlManager::ChangeType changeType) { switch (changeType) { case ControlManager::ControlList: case ControlManager::MasterChanged: createDeviceWidgets(); break; case ControlManager::GUI: updateGuiOptions(); break; case ControlManager::Volume: refreshVolumeLevels(); break; default: ControlManager::warnUnexpectedChangeType(changeType, this); break; } } -void ViewDockAreaPopup::wheelEvent ( QWheelEvent * e ) -{ - if ( _mdws.isEmpty() ) - return; -} - - void ViewDockAreaPopup::configurationUpdate() { // TODO Do we still need configurationUpdate(). It was never implemented for ViewDockAreaPopup } // TODO Currently no right-click, as we have problems to get the ViewDockAreaPopup resized void ViewDockAreaPopup::showContextMenu() { // no right-button-menu on "dock area popup" return; } void ViewDockAreaPopup::resetRefs() { seperatorBetweenMastersAndStreams = 0; separatorBetweenMastersAndStreamsInserted = false; separatorBetweenMastersAndStreamsRequired = false; configureViewButton = 0; restoreVolumeButton1 = 0; restoreVolumeButton2 = 0; restoreVolumeButton3 = 0; restoreVolumeButton4 = 0; mainWindowButton = 0; optionsLayout = 0; _layoutMDW = 0; } void ViewDockAreaPopup::initLayout() { resetMdws(); if (optionsLayout != 0) { QLayoutItem *li2; while ((li2 = optionsLayout->takeAt(0))) delete li2; } // Hint : optionsLayout itself is deleted when "delete _layoutMDW" cascades down if (_layoutMDW != 0) { QLayoutItem *li; while ((li = _layoutMDW->takeAt(0))) delete li; } /* * Strangely enough, I cannot delete optionsLayout in a loop. I get a strange stacktrace: * Application: KMix (kmix), signal: Segmentation fault [...] #6 0x00007f9c9a282900 in QString::shared_null () from /usr/lib/x86_64-linux-gnu/libQtCore.so.4 #7 0x00007f9c9d4286b0 in ViewDockAreaPopup::initLayout (this=0x1272b60) at /home/chris/workspace/kmix-git-trunk/gui/viewdockareapopup.cpp:164 #8 0x00007f9c9d425700 in ViewBase::createDeviceWidgets (this=0x1272b60) at /home/chris/workspace/kmix-git-trunk/gui/viewbase.cpp:137 #9 0x00007f9c9d42845b in ViewDockAreaPopup::controlsChange (this=0x1272b60, changeType=2) at /home/chris/workspace/kmix-git-trunk/gui/viewdockareapopup.cpp:91 */ // if ( optionsLayout != 0 ) // { // QLayoutItem *li2; // while ( ( li2 = optionsLayout->takeAt(0) ) ) // strangely enough, it crashes here // delete li2; // } // --- Due to the strange crash, delete everything manually : START --------------- // I am a bit confused why this doesn't crash. I moved the "optionsLayout->takeAt(0) delete" loop at the beginning, // so the objects should already be deleted. ... delete configureViewButton; delete restoreVolumeButton1; delete restoreVolumeButton2; delete restoreVolumeButton3; delete restoreVolumeButton4; delete mainWindowButton; delete seperatorBetweenMastersAndStreams; // --- Due to the strange crash, delete everything manually : END --------------- resetRefs(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); /* * BKO 299754: Looks like I need to explicitly delete layout(). I have no idea why * "delete _layoutMDW" is not enough, as that is supposed to be the layout * of this ViewDockAreaPopup * (Hint: it might have been 0 already. Nowadays it is definitely, see #resetRefs()) */ delete layout(); // BKO 299754 _layoutMDW = new QGridLayout(this); _layoutMDW->setSpacing(DialogBase::verticalSpacing()); // Review #121166: Add some space over device icons, otherwise they may hit window border _layoutMDW->setContentsMargins(0,5,0,0); //_layoutMDW->setSizeConstraint(QLayout::SetMinimumSize); _layoutMDW->setSizeConstraint(QLayout::SetMaximumSize); _layoutMDW->setObjectName(QLatin1String("KmixPopupLayout")); setLayout(_layoutMDW); // Adding all mixers, as we potentially want to show all master controls. Due to hotplugging // we have to redo the list on each initLayout() (instead of setting it once in the Constructor) _mixers.clear(); QSet preferredMixersForSoundmenu = GlobalConfig::instance().getMixersForSoundmenu(); // qCDebug(KMIX_LOG) << "Launch with " << preferredMixersForSoundmenu; foreach ( Mixer* mixer, Mixer::mixers() ) { bool useMixer = preferredMixersForSoundmenu.isEmpty() || preferredMixersForSoundmenu.contains(mixer->id()); if (useMixer) addMixer(mixer); } // The following loop is for the case when everything gets filtered out. We "reset" to show everything then. // Hint: Filtering everything out can only be an "accident", e.g. when restarting KMix with changed hardware or // backends. if ( _mixers.isEmpty() ) { foreach ( Mixer* mixer, Mixer::mixers() ) { addMixer(mixer); } } // A loop that adds the Master control of each card foreach ( Mixer* mixer, _mixers ) { // qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id(); shared_ptrdockMD = mixer->getLocalMasterMD(); if ( !dockMD && mixer->size() > 0 ) { // If we have no dock device yet, we will take the first available mixer device. dockMD = (*mixer)[0]; } if ( dockMD ) { // qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id(); if ( !dockMD->isApplicationStream() && dockMD->playbackVolume().hasVolume()) { // qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id() << ": YES"; // don't add application streams here. They are handled below, so // we make sure to not add them twice _mixSet.append(dockMD); } } } // loop over all cards // Add all application streams foreach ( Mixer* mixer2 , _mixers ) { foreach ( shared_ptr md, mixer2->getMixSet() ) { if (md->isApplicationStream()) { _mixSet.append(md); } } } } QWidget* ViewDockAreaPopup::add(shared_ptr md) { bool vertical = (GlobalConfig::instance().data.getTraypopupOrientation() == Qt::Vertical); // I am wondering whether using vflags for this would still make sense /* QString dummyMatchAll("*"); QString matchAllPlaybackAndTheCswitch("pvolume,cvolume,pswitch,cswitch"); // Leak | relevant | pctl Each time a stream is added, a new ProfControl gets created. // It cannot be deleted in ~MixDeviceWidget, as ProfControl* ownership is not consistent. // here a new pctl is created (could be deleted), but in ViewSliders the ProcControl is taken from the // MixDevice, which in turn uses it from the GUIProfile. // Summarizing: ProfControl* is either owned by the GUIProfile or created new (ownership unclear). // Hint: dummyMatchAll and matchAllPlaybackAndTheCswitch leak together with pctl ProfControl *pctl = new ProfControl( dummyMatchAll, matchAllPlaybackAndTheCswitch); */ if ( !md->isApplicationStream() ) { separatorBetweenMastersAndStreamsRequired = true; } if ( !separatorBetweenMastersAndStreamsInserted && separatorBetweenMastersAndStreamsRequired && md->isApplicationStream() ) { // First application stream => add separator separatorBetweenMastersAndStreamsInserted = true; int sliderColumn = vertical ? _layoutMDW->columnCount() : _layoutMDW->rowCount(); int row = vertical ? 0 : sliderColumn; int col = vertical ? sliderColumn : 0; seperatorBetweenMastersAndStreams = new QFrame(this); if (vertical) seperatorBetweenMastersAndStreams->setFrameStyle(QFrame::VLine); else seperatorBetweenMastersAndStreams->setFrameStyle(QFrame::HLine); _layoutMDW->addWidget( seperatorBetweenMastersAndStreams, row, col ); //_layoutMDW->addItem( new QSpacerItem( 5, 5 ), row, col ); } if (MatchAllForSoundMenu == 0) { // Lazy init of static member on first use MatchAllForSoundMenu = new ProfControl(ViewDockAreaPopup::InternedString_Star, ViewDockAreaPopup::InternedString_Subcontrols); } ProfControl *pctl = MatchAllForSoundMenu; MixDeviceWidget *mdw = new MDWSlider( md, // only 1 device. true, // Show Mute LE true, // Show Record LED true, // Include Mixer Name false, // Small vertical ? Qt::Vertical : Qt::Horizontal, this, // parent this // NOT ANYMORE!!! -> Is "NULL", so that there is no RMB-popup , pctl ); mdw->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); int sliderColumn = vertical ? _layoutMDW->columnCount() : _layoutMDW->rowCount(); //if (sliderColumn == 1 ) sliderColumn =0; int row = vertical ? 0 : sliderColumn; int col = vertical ? sliderColumn : 0; _layoutMDW->addWidget( mdw, row, col ); //qCDebug(KMIX_LOG) << "ADDED " << md->id() << " at column " << sliderColumn; return mdw; } void ViewDockAreaPopup::constructionFinished() { // qCDebug(KMIX_LOG) << "ViewDockAreaPopup::constructionFinished()\n"; mainWindowButton = new QPushButton(QIcon::fromTheme("show-mixer"), "" , this); mainWindowButton->setObjectName(QLatin1String("MixerPanel")); mainWindowButton->setToolTip(i18n("Show the full mixer window")); connect(mainWindowButton, SIGNAL(clicked()), SLOT(showPanelSlot())); configureViewButton = createConfigureViewButton(); optionsLayout = new QHBoxLayout(); optionsLayout->addWidget(mainWindowButton); optionsLayout->addStretch(1); optionsLayout->addWidget(configureViewButton); #ifdef RESTORE_VOLUME_BUTTON restoreVolumeButton1 = createRestoreVolumeButton(1); optionsLayout->addWidget( restoreVolumeButton1 ); // TODO enable only if user has saved a volume profile // optionsLayout->addWidget( createRestoreVolumeButton(2) ); // optionsLayout->addWidget( createRestoreVolumeButton(3) ); // optionsLayout->addWidget( createRestoreVolumeButton(4) ); #endif int sliderRow = _layoutMDW->rowCount(); _layoutMDW->addLayout(optionsLayout, sliderRow, 0, 1, _layoutMDW->columnCount()); updateGuiOptions(); _layoutMDW->update(); _layoutMDW->activate(); //bool fnc = focusNextChild(); //qCWarning(KMIX_LOG) << "fnc=" <setToolTip(i18n("Load volume profile %1", storageSlot)); profileButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); return profileButton; } + void ViewDockAreaPopup::refreshVolumeLevels() { - foreach ( QWidget* qw, _mdws ) - { - //qCDebug(KMIX_LOG) << "rvl: " << qw; - MixDeviceWidget* mdw = qobject_cast(qw); - if ( mdw != 0 ) mdw->update(); - } + const int num = mixDeviceCount(); + for (int i = 0; i(mixDeviceAt(i)); + if (mdw!=nullptr) mdw->update(); + } } + void ViewDockAreaPopup::configureView() { // Q_ASSERT( !pulseaudioPresent() ); // QSet currentlyActiveMixersInDockArea; // foreach ( Mixer* mixer, _mixers ) // { // currentlyActiveMixersInDockArea.insert(mixer->id()); // } KMixPrefDlg* prefDlg = KMixPrefDlg::getInstance(); //prefDlg->setActiveMixersInDock(currentlyActiveMixersInDockArea); prefDlg->switchToPage(KMixPrefDlg::PrefSoundMenu); } /** * This gets activated whne a user clicks the "Mixer" PushButton in this popup. */ void ViewDockAreaPopup::showPanelSlot() { _kmixMainWindow->setVisible(true); KWindowSystem::setOnDesktop(_kmixMainWindow->winId(), KWindowSystem::currentDesktop()); KWindowSystem::activateWindow(_kmixMainWindow->winId()); // This is only needed when the window is already visible. static_cast(parent())->hide(); } diff --git a/gui/viewdockareapopup.h b/gui/viewdockareapopup.h index 185890df..87dc97ce 100644 --- a/gui/viewdockareapopup.h +++ b/gui/viewdockareapopup.h @@ -1,89 +1,88 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ViewDockAreaPopup_h #define ViewDockAreaPopup_h #include "viewbase.h" #include #include #include "core/ControlManager.h" class QBoxLayout; class QFrame; class QGridLayout; class QWidget; class Mixer; class MixDevice; class KMixWindow; class ViewDockAreaPopup : public ViewBase { Q_OBJECT public: ViewDockAreaPopup(QWidget* parent, QString id, ViewBase::ViewFlags vflags, QString guiProfileId, KMixWindow *dockW); virtual ~ViewDockAreaPopup(); QWidget* add(shared_ptr md) Q_DECL_OVERRIDE; void constructionFinished() Q_DECL_OVERRIDE; void refreshVolumeLevels() Q_DECL_OVERRIDE; void showContextMenu() Q_DECL_OVERRIDE; void configurationUpdate() Q_DECL_OVERRIDE; protected: KMixWindow *_kmixMainWindow; - void wheelEvent ( QWheelEvent * e ) Q_DECL_OVERRIDE; void initLayout() Q_DECL_OVERRIDE; private: QGridLayout* _layoutMDW; QPushButton* createRestoreVolumeButton ( int storageSlot ); bool separatorBetweenMastersAndStreamsInserted; bool separatorBetweenMastersAndStreamsRequired; QFrame* seperatorBetweenMastersAndStreams; QBoxLayout* optionsLayout; QPushButton* configureViewButton; QPushButton *mainWindowButton; QPushButton *restoreVolumeButton1; QPushButton *restoreVolumeButton2; QPushButton *restoreVolumeButton3; QPushButton *restoreVolumeButton4; QIcon restoreVolumeIcon; static ProfControl* MatchAllForSoundMenu; static QString InternedString_Star; static QString InternedString_Subcontrols; public slots: void controlsChange(ControlManager::ChangeType changeType); void configureView() Q_DECL_OVERRIDE; private slots: void showPanelSlot(); void resetRefs(); }; #endif diff --git a/gui/viewsliders.cpp b/gui/viewsliders.cpp index d3c135e0..454b7891 100644 --- a/gui/viewsliders.cpp +++ b/gui/viewsliders.cpp @@ -1,404 +1,405 @@ /* * 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/GlobalConfig.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; Qt::Orientation orientation = GlobalConfig::instance().data.getToplevelOrientation(); if (md->isEnum()) // control is a switch { mdw = new MDWEnum(md, // MixDevice (parameter) orientation, // Orientation this, // parent this, // View widget md->controlProfile()); // profile m_layoutSwitches->addWidget(mdw); } else // control is a slider { mdw = new MDWSlider(md, // MixDevice (parameter) true, // Show Mute LED true, // Show Record LED false, // Include Mixer Name false, // Small orientation, // Orientation this, // parent this, // View widget md->controlProfile()); // profile m_layoutSliders->addWidget(mdw); } return (mdw); } void ViewSliders::initLayout() { resetMdws(); // Our m_layoutSliders now should only contain spacer widgets from the addSpacing() calls in add() above. // We need to trash those too otherwise all sliders gradually migrate away from the edge :p if (m_layoutSliders!=nullptr) { QLayoutItem *li; while ((li = m_layoutSliders->takeAt(0))!=nullptr) delete li; m_layoutSliders = nullptr; } delete m_configureViewButton; m_configureViewButton = nullptr; if (m_layoutSwitches!=nullptr) { QLayoutItem *li; while ((li = m_layoutSwitches->takeAt(0))!=nullptr) delete li; m_layoutSwitches = nullptr; } delete m_layoutMDW; m_layoutMDW = new QGridLayout(this); m_layoutMDW->setContentsMargins(0, 0, 0, 0); m_layoutMDW->setSpacing(0); m_layoutMDW->setRowStretch(0, 1); m_layoutMDW->setColumnStretch(0, 1); if (GlobalConfig::instance().data.getToplevelOrientation()==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 (GlobalConfig::instance().data.getToplevelOrientation()==Qt::Horizontal) { // horizontal sliders // Row 0: Sliders m_layoutMDW->addLayout(m_layoutSliders, 0, 0, 1, -1, Qt::AlignHCenter|Qt::AlignVCenter); // Row 1: Switches m_layoutMDW->addLayout(m_layoutSwitches, 1, 0, Qt::AlignLeft|Qt::AlignTop); } else // vertical sliders { // Column 0: Sliders m_layoutMDW->addLayout(m_layoutSliders, 0, 0, -1, 1, Qt::AlignHCenter|Qt::AlignVCenter); // Column 1: Switches m_layoutMDW->addLayout(m_layoutSwitches, 0, 1, Qt::AlignLeft|Qt::AlignTop); } #ifdef TEST_MIXDEVICE_COMPOSITE QList > mds; // For temporary test #endif // This method iterates over all the controls from the GUI profile. // Each control is checked, whether it is also contained in the mixset, and // applicable for this kind of View. If yes, the control is accepted and inserted. const GUIProfile *guiprof = guiProfile(); if (guiprof!=nullptr) { foreach (Mixer *mixer, _mixers) { const MixSet &mixset = mixer->getMixSet(); foreach (ProfControl *control, guiprof->getControls()) { // The TabName of the control matches this View name (!! attention: Better use some ID, due to i18n() ) QRegExp idRegexp(control->id); // The following for-loop could be simplified by using a std::find_if for (int i = 0; i md = mixset[i]; if (md->id().contains(idRegexp)) { // match found (by name) if (_mixSet.contains(md)) { // check for duplicate already continue; } // Now check whether subcontrols required const bool subcontrolPlaybackWanted = (control->useSubcontrolPlayback() && (md->playbackVolume().hasVolume() || md->hasMuteSwitch())); const bool subcontrolCaptureWanted = (control->useSubcontrolCapture() && (md->captureVolume() .hasVolume() || md->captureVolume() .hasSwitch())); const bool subcontrolEnumWanted = (control->useSubcontrolEnum() && md->isEnum()); const bool subcontrolWanted = subcontrolPlaybackWanted|subcontrolCaptureWanted|subcontrolEnumWanted; if (!subcontrolWanted) continue; md->setControlProfile(control); if (!control->name.isNull()) { // Apply the custom name from the profile // TODO: This is the wrong place. It only // applies to controls in THIS type of view. md->setReadableName(control->name); } if (!control->getSwitchtype().isNull()) { if (control->getSwitchtype()=="On") md->playbackVolume().setSwitchType(Volume::OnSwitch); else if (control->getSwitchtype()=="Off") md->playbackVolume().setSwitchType(Volume::OffSwitch); } _mixSet.append(md); #ifdef TEST_MIXDEVICE_COMPOSITE if ( md->id() == "Front:0" || md->id() == "Surround:0") { // For temporary test mds.append(md); } #endif // No 'break' here, because multiple devices could match // the regexp (e.g. "^.*$") } // name matches } // loop for finding a suitable MixDevice } // iteration over all controls from the Profile } // iteration over all Mixers } // if there is a profile // Show a hint why a tab is empty (dynamic controls!) // TODO: 'visibleControls()==0' could be used for the !isDynamic() case m_emptyStreamHint->setVisible(_mixSet.isEmpty() && isDynamic()); #ifdef TEST_MIXDEVICE_COMPOSITE // @todo: This is currently hardcoded, and instead must be read as usual from the Profile MixDeviceComposite *mdc = new MixDeviceComposite(_mixer, "Composite_Test", mds, "A Composite Control #1", MixDevice::KMIX_COMPOSITE); Volume::ChannelMask chn = Volume::MMAIN; Volume* vol = new Volume( chn, 0, 100, true, true); mdc->addPlaybackVolume(*vol); QString ctlId("Composite_Test"); QString ctlMatchAll("*"); ProfControl* pctl = new ProfControl(ctlId, ctlMatchAll); mdc->setControlProfile(pctl); _mixSet->append(mdc); #endif } void ViewSliders::constructionFinished() { configurationUpdate(); if (!isDynamic()) { // Layout row 1 column 1: Configure View button // TODO: does this need to be a member? m_configureViewButton = createConfigureViewButton(); m_layoutMDW->addWidget(m_configureViewButton, 1, 1, Qt::AlignRight|Qt::AlignBottom); } updateGuiOptions(); } void ViewSliders::configurationUpdate() { // Adjust height of top part by setting it to the maximum of all mdw's int labelExtent = 0; // Find out whether any MDWSlider has Switches. If one has, then we need "extents" - for (int i = 0; i<_mdws.count(); ++i) + + const int num = mixDeviceCount(); + for (int i = 0; i(_mdws[i]); + const MDWSlider *mdw = qobject_cast(mixDeviceAt(i)); if (mdw!=nullptr && mdw->isVisibleTo(this)) { labelExtent = qMax(labelExtent, mdw->labelExtentHint()); //qCDebug(KMIX_LOG) << "########## EXTENT for " << id() << " is " << labelExtent; } } //qCDebug(KMIX_LOG) << "topPartExtent is " << topPartExtent; - for (int i = 0; i<_mdws.count(); ++i) + for (int i = 0; i(_mdws[i]); + MixDeviceWidget *mdw = qobject_cast(mixDeviceAt(i)); if (mdw!=nullptr) { // guiLevel has been set earlier, by inspecting the controls - ProfControl *matchingControl = findMdw(mdw->mixDevice()->id(), guiLevel); + ProfControl *matchingControl = findMdw(mdw->mixDevice()->id()); mdw->setVisible(matchingControl!=nullptr); - MDWSlider *mdwSlider = qobject_cast(_mdws[i]); + MDWSlider *mdwSlider = qobject_cast(mdw); if (mdwSlider!=nullptr && labelExtent>0) { // additional options for sliders mdwSlider->setLabelExtent(labelExtent); } } // if have a MixDeviceWidget } // for all MDW's m_layoutMDW->activate(); } void ViewSliders::refreshVolumeLevels() { - for (int i = 0; i < _mdws.count(); i++) + const int num = mixDeviceCount(); + for (int i = 0; i(mdwx); - if (mdw != 0) + MixDeviceWidget *mdw = qobject_cast(mixDeviceAt(i)); + if (mdw!=nullptr) { // sanity check #ifdef TEST_MIXDEVICE_COMPOSITE // --- start --- The following 4 code lines should be moved to a more // generic place, as it only works in this View. But it // should also work in the ViewDockareaPopup and everywhere else. MixDeviceComposite* mdc = ::qobject_cast(mdw->mixDevice()); if (mdc != 0) { mdc->update(); } // --- end --- #endif if (GlobalConfig::instance().data.debugVolume) { bool debugMe = (mdw->mixDevice()->id() == "PCM:0"); if (debugMe) qCDebug(KMIX_LOG) << "Old PCM:0 playback state" << mdw->mixDevice()->isMuted() << ", vol=" << mdw->mixDevice()->playbackVolume().getAvgVolumePercent(Volume::MALL); } mdw->update(); } else { qCCritical(KMIX_LOG) << "ViewSliders::refreshVolumeLevels(): mdw is not a MixDeviceWidget\n"; // no slider. Cannot happen in theory => skip it } } }