From a11c084f904c21607bd6f925b2280e95d5592082 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 4 Nov 2021 14:47:38 +0100 Subject: Try to group the output configurations in the last tab This introduces a second combobox that is supposed to list the different output configurations for a selected output. The idea is to list the most resonable configration first (i.e. make it the default) and have the primary combobox only list the different outputs themselves. Otherwise the UI might appear quite overwhelming to a novice user, depending on their hardware. Some platforms for exmaple list 4-7 HDMI ports each with stereo, 5.1 and 7.1 configurations. Having all this in a flat list is confusing and bad UX, as it doesn't even fit on a single screen. Unfortunately the pa-api isn't hierarchical and pretty much enumerates the configurations exactly the way they get displayed by pavucontrol, so we need to employ some fuzzy matching logic for grouping, which might or might not need some refining in the future. --- src/cardwidget.cc | 137 ++++++++++++++++++++++++++++++++++++++++++++---------- src/cardwidget.h | 27 +++++++++-- src/cardwidget.ui | 53 ++++----------------- src/mainwindow.cc | 21 +++++++-- 4 files changed, 162 insertions(+), 76 deletions(-) diff --git a/src/cardwidget.cc b/src/cardwidget.cc index d5b4e63..3b4c8b3 100644 --- a/src/cardwidget.cc +++ b/src/cardwidget.cc @@ -24,46 +24,59 @@ #include "cardwidget.h" +#include +#include +#include + /*** CardWidget ***/ CardWidget::CardWidget(QWidget* parent) : QWidget(parent) { setupUi(this); connect(profileList, static_cast(&QComboBox::currentIndexChanged), this, &CardWidget::onProfileChange); - connect(profileCB, &QAbstractButton::toggled, this, &CardWidget::onProfileCheck); + connect(profileFlavorList, static_cast(&QComboBox::currentIndexChanged), this, &CardWidget::onProfileFlavorChange); } void CardWidget::prepareMenu() { int idx = 0; - const bool off = activeProfile == noInOutProfile; profileList->clear(); /* Fill the ComboBox */ - for (const auto & profile : profiles) { - QByteArray name = profile.first; - // skip the "off" profile - if (name == noInOutProfile) - continue; - QString desc = QString::fromUtf8(profile.second); - profileList->addItem(desc, name); - if (profile.first == activeProfile - || (off && profile.first == lastActiveProfile) - ) - { + for (auto k : profiles.keys()) { + auto & profile = profiles[k]; + auto desc = profile.getProfileName(); + profileList->addItem(desc, k); + if (profile.containsProfile(activeProfile)) { profileList->setCurrentIndex(idx); - lastActiveProfile = profile.first; + changeProfile(k); } ++idx; } - - profileCB->setChecked(!off); } -void CardWidget::changeProfile(const QByteArray & name) +void CardWidget::changeProfile(const QString & name) { + auto &profile = profiles[name]; + auto old = profileFlavorList->currentText(); + profileFlavorList->clear(); + int idx = 0; + for (auto & entry : profile.entries) { + auto name = entry.getName(); + profileFlavorList->addItem(name, entry.id); + if (name == old || entry.id == activeProfile) { + profileFlavorList->setCurrentIndex(idx); + } + ++idx; + } + if (profileFlavorList->currentIndex() == -1) { + profileFlavorList->setCurrentIndex(0); + } +} + +void CardWidget::changeProfileFlavor(const QByteArray & id) { pa_operation* o; - if (!(o = pa_context_set_card_profile_by_index(get_context(), index, name.constData(), nullptr, nullptr))) { + if (!(o = pa_context_set_card_profile_by_index(get_context(), index, id.constData(), nullptr, nullptr))) { show_error(tr("pa_context_set_card_profile_by_index() failed").toUtf8().constData()); return; } @@ -76,17 +89,91 @@ void CardWidget::onProfileChange(int active) { return; if (active != -1) - changeProfile(profileList->itemData(active).toByteArray()); + changeProfile(profileList->itemData(active).toString()); } -void CardWidget::onProfileCheck(bool on) -{ +void CardWidget::onProfileFlavorChange(int active) { if (updating) return; - if (on) - onProfileChange(profileList->currentIndex()); - else - changeProfile(noInOutProfile); + if (active != -1) + changeProfileFlavor(profileFlavorList->itemData(active).toByteArray()); +} + +QString ProfileGroup::getProfileName() { + if (!name.isEmpty()) + return name; + // TODO + QHash counts; + qDebug() << "Entries" << this; + for (auto n : entries) { + qDebug() << n.tokens; + QSet tmp = n.tokens.toSet(); //(n.tokens.begin(), n.tokens.end()); + for (auto t : tmp) { + counts[t]++; + } + } + qDebug() << counts; + QSet todo = counts.keys().toSet(); + for (auto &pe : entries) { + QMutableListIterator it(pe.tokens); + while (it.hasNext()) { + auto s = it.next(); + if (counts[s] == entries.size()) { + it.remove(); + if (todo.remove(s)) { + if (!name.isEmpty()) { + name.append(QLatin1Char(' ')); + } + name.append(s); + } + } + } + } + return name; +} + +void ProfileGroup::addEntry(const char *id, const char *name) { + auto *pe = new ProfileEntry; + bool paren = false; + const char *pos = name, *tokenStart = name; + qDebug() << name << "is" << id; + while (*pos != '\0') { + if (!paren && isspace(*pos)) { + if (pos > tokenStart) { + qDebug() << tokenStart; + pe->tokens.append(QString::fromUtf8(tokenStart, pos - tokenStart)); + } + tokenStart = pos + 1; + } + if (paren && *pos == ')') { + qDebug() << tokenStart; + pe->tokens.append(QString::fromUtf8(tokenStart, pos - tokenStart + 1)); + paren = false; + tokenStart = pos + 1; + } + if (tokenStart == pos && *pos == '(') { + paren = true; + } + pos++; + } + if (pos > tokenStart) { + qDebug() << tokenStart; + pe->tokens.append(QString::fromUtf8(tokenStart, pos - tokenStart)); + } + pe->id = id; + this->entries.append(*pe); + delete pe; +} + +QString ProfileEntry::getName() const { + return tokens.join(QLatin1String(" ")); +} +bool ProfileGroup::containsProfile(const QByteArray &pro) const { + for (const auto &e : this->entries) { + if (e.id == pro) + return true; + } + return false; } diff --git a/src/cardwidget.h b/src/cardwidget.h index 71f6ebc..7bd87d2 100644 --- a/src/cardwidget.h +++ b/src/cardwidget.h @@ -24,6 +24,23 @@ #include "pavucontrol.h" #include "ui_cardwidget.h" #include +#include +#include +#include + +struct ProfileEntry { + QByteArray id; + QStringList tokens; + QString getName() const; +}; + +struct ProfileGroup { + QString name; + QList entries; + QString getProfileName(); + void addEntry(const char* id, const char* name); + bool containsProfile(const QByteArray &pro) const; +}; class PortInfo { public: @@ -45,20 +62,20 @@ public: uint32_t index; bool updating; - std::vector< std::pair > profiles; + + QMap profiles; std::map ports; QByteArray activeProfile; - QByteArray noInOutProfile; - QByteArray lastActiveProfile; bool hasSinks; bool hasSources; void prepareMenu(); protected: - void changeProfile(const QByteArray & name); + void changeProfile(const QString & name); + void changeProfileFlavor(const QByteArray & id); void onProfileChange(int active); - void onProfileCheck(bool on); + void onProfileFlavorChange(int active); }; diff --git a/src/cardwidget.ui b/src/cardwidget.ui index 28ea7ab..a75654b 100644 --- a/src/cardwidget.ui +++ b/src/cardwidget.ui @@ -7,7 +7,7 @@ 0 0 368 - 79 + 106 @@ -33,14 +33,7 @@ - - - - - true - - - + @@ -53,6 +46,13 @@ + + + + + + + @@ -63,38 +63,5 @@ - - - profileCB - toggled(bool) - profileLabel - setEnabled(bool) - - - 137 - 47 - - - 147 - 47 - - - - - profileCB - toggled(bool) - profileList - setEnabled(bool) - - - 137 - 47 - - - 315 - 47 - - - - + diff --git a/src/mainwindow.cc b/src/mainwindow.cc index 76e81bf..66c0ac4 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -22,6 +22,7 @@ #include #endif +#include #include #include "mainwindow.h" @@ -258,9 +259,23 @@ void MainWindow::updateCard(const pa_card_info &info) { if (!p_profile->available) desc += tr(" (unavailable)").toUtf8().constData(); - w->profiles.push_back(std::pair(p_profile->name, desc)); - if (p_profile->n_sinks == 0 && p_profile->n_sources == 0) - w->noInOutProfile = p_profile->name; + QString parseId = QString::fromUtf8(p_profile->name); + int plus = parseId.indexOf(QLatin1Char('+')); + if (plus != -1) { + parseId = parseId.left(plus); + } + auto list = parseId.split(QRegularExpression(QLatin1String("[:\\-]"))); + parseId.clear(); + for (auto p : list) { + if (p == QLatin1String("input") || p == QLatin1String("output")) + continue; + if (parseId.isEmpty() || p.startsWith(QLatin1String("extra"))) { + parseId.append(p); + } + } + qDebug() << "ParseID:" << parseId; + ProfileGroup &group = w->profiles[parseId]; + group.addEntry(p_profile->name, desc.constData()); } w->activeProfile = info.active_profile ? info.active_profile->name : ""; -- cgit v1.2.3-55-g7522