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 <device>" or " from <device>"
where the <device> 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.
This commit is contained in:
Colin Guthrie 2009-06-13 19:15:02 +01:00
parent e71562619a
commit 02b316fcba
11 changed files with 254 additions and 213 deletions

View File

@ -86,9 +86,6 @@ MainWindow::MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade:
sourceOutputTypeComboBox->signal_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<uint32_t, SinkWidget*>::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<uint32_t, SinkInputWidget*>::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<uint32_t, SourceWidget*>::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<uint32_t, SourceOutputWidget*>::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<uint32_t, SinkInputWidget*>::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<uint32_t, SourceOutputWidget*>::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();
}

View File

@ -91,31 +91,8 @@ public:
Glib::ustring defaultSinkName, defaultSourceName;
class DeviceColumns : public Gtk::TreeModel::ColumnRecord
{
public:
DeviceColumns()
{ add(index); add(name); }
Gtk::TreeModelColumn<uint32_t> index;
Gtk::TreeModelColumn<Glib::ustring> name;
};
DeviceColumns deviceColumns;
Glib::RefPtr<Gtk::ListStore> sinkTree;
std::map<uint32_t, uint32_t> sinkTreeIndexes;
Glib::RefPtr<Gtk::ListStore> sourceTree;
std::map<uint32_t, uint32_t> sourceTreeIndexes;
protected:
virtual void on_realize();
private:
void rebuildSinkCombo();
void rebuildSourceCombo();
};

View File

@ -498,7 +498,6 @@ Monitors</property>
<widget class="GtkImage" id="iconImage">
<property name="visible">True</property>
<property name="stock">gtk-missing-image</property>
<property name="icon-size">4</property>
</widget>
<packing>
<property name="expand">False</property>
@ -508,6 +507,7 @@ Monitors</property>
<child>
<widget class="GtkHBox" id="hbox12">
<property name="visible">True</property>
<property name="spacing">2</property>
<child>
<widget class="GtkLabel" id="boldNameLabel">
<property name="visible">True</property>
@ -524,12 +524,42 @@ Monitors</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Stream Title</property>
<property name="use_markup">True</property>
<property name="ellipsize">middle</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="directionLabel">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xpad">4</property>
<property name="label" translatable="yes">direction</property>
<property name="use_markup">True</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<widget class="GtkEventBox" id="deviceButton">
<property name="visible">True</property>
<child>
<widget class="GtkLabel" id="deviceLabel">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;a href=""&gt;Device&lt;/a&gt;</property>
<property name="use_markup">True</property>
</widget>
</child>
</widget>
<packing>
<property name="position">3</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
@ -582,6 +612,26 @@ Monitors</property>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="terminateButton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="has_tooltip">True</property>
<property name="tooltip" translatable="yes">Terminate stream</property>
<child>
<widget class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="stock">gtk-delete</property>
</widget>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
@ -612,52 +662,6 @@ Monitors</property>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="streamControlHBox">
<property name="visible">True</property>
<child>
<widget class="GtkLabel" id="directionLabel">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">direction</property>
<property name="use_markup">True</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkComboBox" id="deviceCombo">
<property name="visible">True</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="terminateButton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip" translatable="yes">Terminate stream</property>
<child>
<widget class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="stock">gtk-delete</property>
</widget>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
</widget>
<packing>
<property name="position">2</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
@ -702,7 +706,6 @@ Monitors</property>
<widget class="GtkImage" id="iconImage">
<property name="visible">True</property>
<property name="stock">gtk-missing-image</property>
<property name="icon-size">4</property>
</widget>
<packing>
<property name="expand">False</property>
@ -947,7 +950,6 @@ Monitors</property>
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="stock">gtk-missing-image</property>
<property name="icon-size">4</property>
</widget>
<packing>
<property name="expand">False</property>

View File

@ -32,7 +32,8 @@ RoleWidget::RoleWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade:
StreamWidget(cobject, x) {
lockToggleButton->hide();
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;

View File

@ -33,7 +33,6 @@ public:
Glib::ustring role;
Glib::ustring device;
virtual void onDeviceChange();
virtual void onMuteToggleButton();
virtual void executeVolumeUpdate();
};

View File

@ -32,13 +32,13 @@ SinkInputWidget::SinkInputWidget(BaseObjectType* cobject, const Glib::RefPtr<Gno
StreamWidget(cobject, x),
mpMainWindow(NULL) {
directionLabel->set_label(_("<i>Playing on </i> "));
gchar *txt;
directionLabel->set_label(txt = g_markup_printf_escaped("<i>%s</i>", _("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("<a href=\"\">%s</a>", w->description.c_str());
}
else
txt = g_markup_printf_escaped("<a href=\"\">%s</a>", _("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;
void SinkInputWidget::clearMenu() {
while (!sinkMenuItems.empty()) {
std::map<uint32_t, SinkMenuItem*>::iterator i = sinkMenuItems.begin();
delete i->second;
sinkMenuItems.erase(i);
}
}
if (updating || mSuppressDeviceChange)
void SinkInputWidget::buildMenu() {
for (std::map<uint32_t, SinkWidget*>::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;
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 (!menuItem.get_active())
return;
if (!(o = pa_context_move_sink_input_by_index(get_context(), index, sink_index, NULL, NULL))) {
/*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;
}

View File

@ -31,6 +31,7 @@ class SinkInputWidget : public StreamWidget {
public:
SinkInputWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& 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<uint32_t, SinkMenuItem*> sinkMenuItems;
};
#endif

View File

@ -32,13 +32,13 @@ SourceOutputWidget::SourceOutputWidget(BaseObjectType* cobject, const Glib::RefP
StreamWidget(cobject, x),
mpMainWindow(NULL) {
directionLabel->set_label(_("<i>Recording from </i> "));
gchar *txt;
directionLabel->set_label(txt = g_markup_printf_escaped("<i>%s</i>", _("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("<a href=\"\">%s</a>", w->description.c_str());
}
else
txt = g_markup_printf_escaped("<a href=\"\">%s</a>", _("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)
void SourceOutputWidget::clearMenu() {
while (!sourceMenuItems.empty()) {
std::map<uint32_t, SourceMenuItem*>::iterator i = sourceMenuItems.begin();
delete i->second;
sourceMenuItems.erase(i);
}
}
void SourceOutputWidget::buildMenu() {
for (std::map<uint32_t, SourceWidget*>::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;
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 (!menuItem.get_active())
return;
if (!(o = pa_context_move_source_output_by_index(get_context(), source_index, index, NULL, NULL))) {
/*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;
}

View File

@ -31,6 +31,7 @@ class SourceOutputWidget : public StreamWidget {
public:
SourceOutputWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& 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<uint32_t, SourceMenuItem*> sourceMenuItems;
};
#endif

View File

@ -27,19 +27,18 @@
/*** StreamWidget ***/
StreamWidget::StreamWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& 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);
x->get_widget("deviceButton", deviceButton);
x->get_widget("deviceLabel", deviceLabel);
deviceCombo->signal_changed().connect( sigc::mem_fun(*this, &StreamWidget::onDeviceChange));
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;
}

View File

@ -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