From 02b316fcba8a112e528a0bddfcb93cf6f3179168 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sat, 13 Jun 2009 19:15:02 +0100 Subject: [PATCH] More changes in the UI to try and make things neater. This abandons the combo box approach an instead partially reverts to the popup. We now display a suffix after the stream title saying " on " or " from " where the part looks like a hyperlink and, when clicked, shows the popup to change the device. If there is only one device available, we suppress the whole thing and thus avoid confusion. --- src/mainwindow.cc | 79 ++++++----------------------- src/mainwindow.h | 23 --------- src/pavucontrol.glade | 102 +++++++++++++++++++------------------- src/rolewidget.cc | 6 +-- src/rolewidget.h | 1 - src/sinkinputwidget.cc | 91 +++++++++++++++++++++++----------- src/sinkinputwidget.h | 25 +++++++++- src/sourceoutputwidget.cc | 90 ++++++++++++++++++++++----------- src/sourceoutputwidget.h | 25 +++++++++- src/streamwidget.cc | 15 +++--- src/streamwidget.h | 10 ++-- 11 files changed, 254 insertions(+), 213 deletions(-) diff --git a/src/mainwindow.cc b/src/mainwindow.cc index f76f37a..5e6b464 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -86,9 +86,6 @@ MainWindow::MainWindow(BaseObjectType* cobject, const Glib::RefPtrsignal_changed().connect(sigc::mem_fun(*this, &MainWindow::onSourceOutputTypeComboBoxChanged)); sinkTypeComboBox->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::onSinkTypeComboBoxChanged)); sourceTypeComboBox->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::onSourceTypeComboBoxChanged)); - - sinkTree = Gtk::ListStore::create(deviceColumns); - sourceTree = Gtk::ListStore::create(deviceColumns); } MainWindow* MainWindow::create() { @@ -179,34 +176,6 @@ void MainWindow::updateCard(const pa_card_info &info) { updateDeviceVisibility(); } -void MainWindow::rebuildSinkCombo() { - uint32_t idx = 0; - Gtk::TreeModel::Row row; - - sinkTree->clear(); - sinkTreeIndexes.clear(); - - /* - row = *(sinkTree->append()); - idx++; - row[deviceColumns.index] = -1; - row[deviceColumns.name] = "Default Output"; - */ - - for (std::map::iterator i = sinkWidgets.begin(); i != sinkWidgets.end(); ++i) { - Gtk::TreeModel::Row row = *(sinkTree->append()); - sinkTreeIndexes[i->first] = idx++; - row[deviceColumns.index] = i->first; - row[deviceColumns.name] = i->second->description.c_str(); - } - - /* Force a redraw of the dropdown combo due to the model change. */ - for (std::map::iterator i = sinkInputWidgets.begin(); i != sinkInputWidgets.end(); ++i) { - SinkInputWidget* w = i->second; - w->setSinkIndex(w->sinkIndex()); - } -} - void MainWindow::updateSink(const pa_sink_info &info) { SinkWidget *w; bool is_new = false; @@ -250,7 +219,6 @@ void MainWindow::updateSink(const pa_sink_info &info) { w->updating = false; - rebuildSinkCombo(); if (is_new) updateDeviceVisibility(); } @@ -356,34 +324,6 @@ void MainWindow::createMonitorStreamForSinkInput(uint32_t sink_input_idx, uint32 } } -void MainWindow::rebuildSourceCombo() { - uint32_t idx = 0; - Gtk::TreeModel::Row row; - - sourceTree->clear(); - sourceTreeIndexes.clear(); - - /* - row = *(sourceTree->append()); - idx++; - row[deviceColumns.index] = -1; - row[deviceColumns.name] = "Default Input"; - */ - - for (std::map::iterator i = sourceWidgets.begin(); i != sourceWidgets.end(); ++i) { - Gtk::TreeModel::Row row = *(sourceTree->append()); - sourceTreeIndexes[i->first] = idx++; - row[deviceColumns.index] = i->first; - row[deviceColumns.name] = i->second->description.c_str(); - } - - /* Force a redraw of the dropdown combo due to the model change. */ - for (std::map::iterator i = sourceOutputWidgets.begin(); i != sourceOutputWidgets.end(); ++i) { - SourceOutputWidget* w = i->second; - w->setSourceIndex(w->sourceIndex()); - } -} - void MainWindow::updateSource(const pa_source_info &info) { SourceWidget *w; bool is_new = false; @@ -428,7 +368,6 @@ void MainWindow::updateSource(const pa_source_info &info) { w->updating = false; - rebuildSourceCombo(); if (is_new) updateDeviceVisibility(); } @@ -737,6 +676,14 @@ void MainWindow::reallyUpdateDeviceVisibility() { for (std::map::iterator i = sinkInputWidgets.begin(); i != sinkInputWidgets.end(); ++i) { SinkInputWidget* w = i->second; + if (sinkWidgets.size() > 1) { + w->directionLabel->show(); + w->deviceButton->show(); + } else { + w->directionLabel->hide(); + w->deviceButton->hide(); + } + if (showSinkInputType == SINK_INPUT_ALL || w->type == showSinkInputType) { w->show(); is_empty = false; @@ -757,6 +704,14 @@ void MainWindow::reallyUpdateDeviceVisibility() { for (std::map::iterator i = sourceOutputWidgets.begin(); i != sourceOutputWidgets.end(); ++i) { SourceOutputWidget* w = i->second; + if (sourceWidgets.size() > 1) { + w->directionLabel->show(); + w->deviceButton->show(); + } else { + w->directionLabel->hide(); + w->deviceButton->hide(); + } + if (showSourceOutputType == SOURCE_OUTPUT_ALL || w->type == showSourceOutputType) { w->show(); is_empty = false; @@ -848,7 +803,6 @@ void MainWindow::removeSink(uint32_t index) { delete sinkWidgets[index]; sinkWidgets.erase(index); - rebuildSinkCombo(); updateDeviceVisibility(); } @@ -858,7 +812,6 @@ void MainWindow::removeSource(uint32_t index) { delete sourceWidgets[index]; sourceWidgets.erase(index); - rebuildSourceCombo(); updateDeviceVisibility(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 48fcc76..a9f0f64 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -91,31 +91,8 @@ public: Glib::ustring defaultSinkName, defaultSourceName; - class DeviceColumns : public Gtk::TreeModel::ColumnRecord - { - public: - - DeviceColumns() - { add(index); add(name); } - - Gtk::TreeModelColumn index; - Gtk::TreeModelColumn name; - }; - - DeviceColumns deviceColumns; - - Glib::RefPtr sinkTree; - std::map sinkTreeIndexes; - - Glib::RefPtr sourceTree; - std::map sourceTreeIndexes; - protected: virtual void on_realize(); - -private: - void rebuildSinkCombo(); - void rebuildSourceCombo(); }; diff --git a/src/pavucontrol.glade b/src/pavucontrol.glade index 5f13333..07d1a36 100644 --- a/src/pavucontrol.glade +++ b/src/pavucontrol.glade @@ -498,7 +498,6 @@ Monitors True gtk-missing-image - 4 False @@ -508,6 +507,7 @@ Monitors True + 2 True @@ -524,12 +524,42 @@ Monitors 0 Stream Title True - middle + False 1 + + + True + 0 + 4 + direction + True + + + False + 2 + + + + + True + + + True + True + 0 + <a href="">Device</a> + True + + + + + 3 + + 1 @@ -582,6 +612,26 @@ Monitors 1 + + + True + True + True + True + Terminate stream + + + True + gtk-delete + + + + + False + False + 2 + + False @@ -612,52 +662,6 @@ Monitors 1 - - - True - - - True - 0 - direction - True - - - False - 0 - - - - - True - - - 1 - - - - - True - True - True - Terminate stream - - - True - gtk-delete - - - - - False - 2 - - - - - 2 - - False @@ -702,7 +706,6 @@ Monitors True gtk-missing-image - 4 False @@ -947,7 +950,6 @@ Monitors True 0 gtk-missing-image - 4 False diff --git a/src/rolewidget.cc b/src/rolewidget.cc index 54c64d5..4da818f 100644 --- a/src/rolewidget.cc +++ b/src/rolewidget.cc @@ -32,7 +32,8 @@ RoleWidget::RoleWidget(BaseObjectType* cobject, const Glib::RefPtrhide(); - streamControlHBox->hide(); + directionLabel->hide(); + deviceButton->hide(); } RoleWidget* RoleWidget::create() { @@ -48,9 +49,6 @@ void RoleWidget::onMuteToggleButton() { executeVolumeUpdate(); } -void RoleWidget::onDeviceChange() { -} - void RoleWidget::executeVolumeUpdate() { pa_ext_stream_restore_info info; diff --git a/src/rolewidget.h b/src/rolewidget.h index 955364c..bbd39d6 100644 --- a/src/rolewidget.h +++ b/src/rolewidget.h @@ -33,7 +33,6 @@ public: Glib::ustring role; Glib::ustring device; - virtual void onDeviceChange(); virtual void onMuteToggleButton(); virtual void executeVolumeUpdate(); }; diff --git a/src/sinkinputwidget.cc b/src/sinkinputwidget.cc index 012e834..e5307b7 100644 --- a/src/sinkinputwidget.cc +++ b/src/sinkinputwidget.cc @@ -32,13 +32,13 @@ SinkInputWidget::SinkInputWidget(BaseObjectType* cobject, const Glib::RefPtrset_label(_("Playing on ")); + gchar *txt; + directionLabel->set_label(txt = g_markup_printf_escaped("%s", _("on"))); + g_free(txt); } void SinkInputWidget::init(MainWindow* mainWindow) { mpMainWindow = mainWindow; - deviceCombo->set_model(mpMainWindow->sinkTree); - deviceCombo->pack_start(mpMainWindow->deviceColumns.name); } SinkInputWidget* SinkInputWidget::create(MainWindow* mainWindow) { @@ -49,12 +49,22 @@ SinkInputWidget* SinkInputWidget::create(MainWindow* mainWindow) { return w; } +SinkInputWidget::~SinkInputWidget(void) { + clearMenu(); +} + void SinkInputWidget::setSinkIndex(uint32_t idx) { mSinkIndex = idx; - mSuppressDeviceChange = true; - deviceCombo->set_active(mpMainWindow->sinkTreeIndexes[idx]); - mSuppressDeviceChange = false; + gchar *txt; + if (mpMainWindow->sinkWidgets.count(idx)) { + SinkWidget *w = mpMainWindow->sinkWidgets[idx]; + txt = g_markup_printf_escaped("%s", w->description.c_str()); + } + else + txt = g_markup_printf_escaped("%s", _("Unknown output")); + deviceLabel->set_label(txt); + g_free(txt); } uint32_t SinkInputWidget::sinkIndex() { @@ -97,27 +107,50 @@ void SinkInputWidget::onKill() { pa_operation_unref(o); } -void SinkInputWidget::onDeviceChange() { - Gtk::TreeModel::iterator iter; - - if (updating || mSuppressDeviceChange) - return; - - iter = deviceCombo->get_active(); - if (iter) - { - Gtk::TreeModel::Row row = *iter; - if (row) - { - pa_operation* o; - uint32_t sink_index = row[mpMainWindow->deviceColumns.index]; - - if (!(o = pa_context_move_sink_input_by_index(get_context(), index, sink_index, NULL, NULL))) { - show_error(_("pa_context_move_sink_input_by_index() failed")); - return; - } - - pa_operation_unref(o); - } - } +void SinkInputWidget::clearMenu() { + while (!sinkMenuItems.empty()) { + std::map::iterator i = sinkMenuItems.begin(); + delete i->second; + sinkMenuItems.erase(i); + } +} + +void SinkInputWidget::buildMenu() { + for (std::map::iterator i = mpMainWindow->sinkWidgets.begin(); i != mpMainWindow->sinkWidgets.end(); ++i) { + SinkMenuItem *m; + sinkMenuItems[i->second->index] = m = new SinkMenuItem(this, i->second->description.c_str(), i->second->index, i->second->index == mSinkIndex); + menu.append(m->menuItem); + } + menu.show_all(); +} + +void SinkInputWidget::SinkMenuItem::onToggle() { + if (widget->updating) + return; + + if (!menuItem.get_active()) + return; + + /*if (!mpMainWindow->sinkWidgets.count(widget->index)) + return;*/ + + pa_operation* o; + if (!(o = pa_context_move_sink_input_by_index(get_context(), widget->index, index, NULL, NULL))) { + show_error(_("pa_context_move_sink_input_by_index() failed")); + return; + } + + pa_operation_unref(o); +} + +bool SinkInputWidget::onDeviceChangePopup(GdkEventButton* event) { + if (GDK_BUTTON_PRESS == event->type && 1 == event->button) + { + clearMenu(); + buildMenu(); + menu.popup(event->button, event->time); + return true; + } + else + return false; } diff --git a/src/sinkinputwidget.h b/src/sinkinputwidget.h index 503c67c..b48a802 100644 --- a/src/sinkinputwidget.h +++ b/src/sinkinputwidget.h @@ -31,6 +31,7 @@ class SinkInputWidget : public StreamWidget { public: SinkInputWidget(BaseObjectType* cobject, const Glib::RefPtr& x); static SinkInputWidget* create(MainWindow* mainWindow); + ~SinkInputWidget(void); void init(MainWindow* mainWindow); @@ -40,7 +41,7 @@ public: void setSinkIndex(uint32_t idx); uint32_t sinkIndex(); virtual void executeVolumeUpdate(); - virtual void onDeviceChange(); + virtual bool onDeviceChangePopup(GdkEventButton*); virtual void onMuteToggleButton(); virtual void onKill(); @@ -48,6 +49,28 @@ private: MainWindow *mpMainWindow; uint32_t mSinkIndex; + void clearMenu(); + void buildMenu(); + + Gtk::Menu menu; + + struct SinkMenuItem { + SinkMenuItem(SinkInputWidget *w, const char *label, uint32_t i, bool active) : + widget(w), + menuItem(label), + index(i) { + menuItem.set_active(active); + menuItem.set_draw_as_radio(true); + menuItem.signal_toggled().connect(sigc::mem_fun(*this, &SinkMenuItem::onToggle)); + } + + SinkInputWidget *widget; + Gtk::CheckMenuItem menuItem; + uint32_t index; + void onToggle(); + }; + + std::map sinkMenuItems; }; #endif diff --git a/src/sourceoutputwidget.cc b/src/sourceoutputwidget.cc index 19ecda7..fb3f441 100644 --- a/src/sourceoutputwidget.cc +++ b/src/sourceoutputwidget.cc @@ -32,13 +32,13 @@ SourceOutputWidget::SourceOutputWidget(BaseObjectType* cobject, const Glib::RefP StreamWidget(cobject, x), mpMainWindow(NULL) { - directionLabel->set_label(_("Recording from ")); + gchar *txt; + directionLabel->set_label(txt = g_markup_printf_escaped("%s", _("from"))); + g_free(txt); } void SourceOutputWidget::init(MainWindow* mainWindow) { mpMainWindow = mainWindow; - deviceCombo->set_model(mpMainWindow->sourceTree); - deviceCombo->pack_start(mpMainWindow->deviceColumns.name); } SourceOutputWidget* SourceOutputWidget::create(MainWindow* mainWindow) { @@ -49,12 +49,22 @@ SourceOutputWidget* SourceOutputWidget::create(MainWindow* mainWindow) { return w; } +SourceOutputWidget::~SourceOutputWidget(void) { + clearMenu(); +} + void SourceOutputWidget::setSourceIndex(uint32_t idx) { mSourceIndex = idx; - mSuppressDeviceChange = true; - deviceCombo->set_active(mpMainWindow->sourceTreeIndexes[idx]); - mSuppressDeviceChange = false; + gchar *txt; + if (mpMainWindow->sourceWidgets.count(idx)) { + SourceWidget *w = mpMainWindow->sourceWidgets[idx]; + txt = g_markup_printf_escaped("%s", w->description.c_str()); + } + else + txt = g_markup_printf_escaped("%s", _("Unknown input")); + deviceLabel->set_label(txt); + g_free(txt); } uint32_t SourceOutputWidget::sourceIndex() { @@ -71,27 +81,51 @@ void SourceOutputWidget::onKill() { pa_operation_unref(o); } -void SourceOutputWidget::onDeviceChange() { - Gtk::TreeModel::iterator iter; - if (updating || mSuppressDeviceChange) - return; - - iter = deviceCombo->get_active(); - if (iter) - { - Gtk::TreeModel::Row row = *iter; - if (row) - { - pa_operation* o; - uint32_t source_index = row[mpMainWindow->deviceColumns.index]; - - if (!(o = pa_context_move_source_output_by_index(get_context(), source_index, index, NULL, NULL))) { - show_error(_("pa_context_move_source_output_by_index() failed")); - return; - } - - pa_operation_unref(o); - } - } +void SourceOutputWidget::clearMenu() { + while (!sourceMenuItems.empty()) { + std::map::iterator i = sourceMenuItems.begin(); + delete i->second; + sourceMenuItems.erase(i); + } +} + +void SourceOutputWidget::buildMenu() { + for (std::map::iterator i = mpMainWindow->sourceWidgets.begin(); i != mpMainWindow->sourceWidgets.end(); ++i) { + SourceMenuItem *m; + sourceMenuItems[i->second->index] = m = new SourceMenuItem(this, i->second->description.c_str(), i->second->index, i->second->index == mSourceIndex); + menu.append(m->menuItem); + } + menu.show_all(); +} + +void SourceOutputWidget::SourceMenuItem::onToggle() { + if (widget->updating) + return; + + if (!menuItem.get_active()) + return; + + /*if (!mpMainWindow->sourceWidgets.count(widget->index)) + return;*/ + + pa_operation* o; + if (!(o = pa_context_move_source_output_by_index(get_context(), widget->index, index, NULL, NULL))) { + show_error(_("pa_context_move_source_output_by_index() failed")); + return; + } + + pa_operation_unref(o); +} + +bool SourceOutputWidget::onDeviceChangePopup(GdkEventButton* event) { + if (GDK_BUTTON_PRESS == event->type && 1 == event->button) + { + clearMenu(); + buildMenu(); + menu.popup(event->button, event->time); + return true; + } + else + return false; } diff --git a/src/sourceoutputwidget.h b/src/sourceoutputwidget.h index 0d43cc9..ecf937f 100644 --- a/src/sourceoutputwidget.h +++ b/src/sourceoutputwidget.h @@ -31,6 +31,7 @@ class SourceOutputWidget : public StreamWidget { public: SourceOutputWidget(BaseObjectType* cobject, const Glib::RefPtr& x); static SourceOutputWidget* create(MainWindow* mainWindow); + ~SourceOutputWidget(void); void init(MainWindow* mainWindow); @@ -39,13 +40,35 @@ public: uint32_t index, clientIndex; void setSourceIndex(uint32_t idx); uint32_t sourceIndex(); - virtual void onDeviceChange(); + virtual bool onDeviceChangePopup(GdkEventButton*); virtual void onKill(); private: MainWindow *mpMainWindow; uint32_t mSourceIndex; + void clearMenu(); + void buildMenu(); + + Gtk::Menu menu; + + struct SourceMenuItem { + SourceMenuItem(SourceOutputWidget *w, const char *label, uint32_t i, bool active) : + widget(w), + menuItem(label), + index(i) { + menuItem.set_active(active); + menuItem.set_draw_as_radio(true); + menuItem.signal_toggled().connect(sigc::mem_fun(*this, &SourceMenuItem::onToggle)); + } + + SourceOutputWidget *widget; + Gtk::CheckMenuItem menuItem; + uint32_t index; + void onToggle(); + }; + + std::map sourceMenuItems; }; #endif diff --git a/src/streamwidget.cc b/src/streamwidget.cc index b739676..fd737fd 100644 --- a/src/streamwidget.cc +++ b/src/streamwidget.cc @@ -27,19 +27,18 @@ /*** StreamWidget ***/ StreamWidget::StreamWidget(BaseObjectType* cobject, const Glib::RefPtr& x) : - MinimalStreamWidget(cobject, x), - mSuppressDeviceChange(false) { + MinimalStreamWidget(cobject, x) { x->get_widget("lockToggleButton", lockToggleButton); x->get_widget("muteToggleButton", muteToggleButton); - x->get_widget("deviceCombo", deviceCombo); x->get_widget("terminateButton", terminateButton); x->get_widget("directionLabel", directionLabel); - x->get_widget("streamControlHBox", streamControlHBox); - - deviceCombo->signal_changed().connect( sigc::mem_fun(*this, &StreamWidget::onDeviceChange)); + x->get_widget("deviceButton", deviceButton); + x->get_widget("deviceLabel", deviceLabel); + terminateButton->signal_clicked().connect(sigc::mem_fun(*this, &StreamWidget::onKill)); muteToggleButton->signal_clicked().connect(sigc::mem_fun(*this, &StreamWidget::onMuteToggleButton)); + deviceButton->signal_button_press_event().connect(sigc::mem_fun(*this, &StreamWidget::onDeviceChangePopup)); for (unsigned i = 0; i < PA_CHANNELS_MAX; i++) channelWidgets[i] = NULL; @@ -109,3 +108,7 @@ bool StreamWidget::timeoutEvent() { void StreamWidget::executeVolumeUpdate() { } + +bool StreamWidget::onDeviceChangePopup(GdkEventButton*) { + return false; +} diff --git a/src/streamwidget.h b/src/streamwidget.h index 6dfb67c..48e3fd6 100644 --- a/src/streamwidget.h +++ b/src/streamwidget.h @@ -38,7 +38,8 @@ public: Gtk::ToggleButton *lockToggleButton, *muteToggleButton; Gtk::Button *terminateButton; Gtk::Label *directionLabel; - Gtk::HBox *streamControlHBox; + Gtk::EventBox *deviceButton; + Gtk::Label *deviceLabel; pa_channel_map channelMap; pa_cvolume volume; @@ -46,7 +47,7 @@ public: ChannelWidget *channelWidgets[PA_CHANNELS_MAX]; virtual void onMuteToggleButton(); - virtual void onDeviceChange() = 0; + virtual bool onDeviceChangePopup(GdkEventButton*); virtual void onKill(); sigc::connection timeoutConnection; @@ -54,11 +55,6 @@ public: bool timeoutEvent(); virtual void executeVolumeUpdate(); - -protected: - Gtk::ComboBox *deviceCombo; - - bool mSuppressDeviceChange; }; #endif