Gtkmm: Dados extra em Combo Box de maneira fácil

Por Jean Hertel, 31/07/2020

gtkmm , gtk+ , combobox

Recentemente fiz um grande refactoring do adriconf e uma das coisas que finalmente decidi corrigir foi o gerenciamento de combo box. Para configurar qualquer opção no hardware, temos que perguntar quais opções são suportadas e portanto desenhamos dinamicamente os campos de configuração. Por causa disso é necessário localizar dinamicamente qual campo estamos alterando e então fazer o update.

No HTML os Combo Box suportam dois valores: um texto de exibição e o valor verdadeiro do campo. Eu queria o mesmo tipo de funcionalidade usando GTKmm, porem o processo é um pouco doloroso.

Os ComboBox no GTKmm são bem flexiveis, mas isso vem com uma complexidade. Se você quiser apenas texto pode usar o ComboBoxText:

Gtk::ComboBoxText myCombo;

myCombo.append("Option 1");
myCombo.append("Ninjas!");
myCombo.append("Samurais");
myCombo.set_active(1);

// Ver opção selecionada
Glib::ustring text = myCombo.get_active_text();

Este exemplo parece bem simples, porem para adicionar campos extras, precisamos fazer um processo bem mais complicado. A documentação oficial já cobre bem isto, portanto se quiser ver em detalhes como desenhar um campo com mais opções ou outras coisas apenas veja a documentação aqui.

No meu caso eu queria ter um ponteiro para o objeto que estou alterando mas sem ter que lidar com toda a maluquice de models e renderers. Por causa disso, escrevi as classes abaixo:

#include "gtkmm.h"

template<class T>
class ExtraDataTreeModel : public Gtk::TreeModel::ColumnRecord {
public:
    Gtk::TreeModelColumn<Glib::ustring> displayName;
    Gtk::TreeModelColumn<T> extraData;

    ExtraDataTreeModel() {
        add(displayName);
        add(extraData);
    }
};

template<class T>
class ComboBoxExtra : public Gtk::ComboBox {
private:
    ExtraDataTreeModel<T> treeModel;

public:
    // Permite chamadas por Glade Builder
    ComboBoxExtra(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade) : Gtk::ComboBox(cobject) {
        auto listStore = Gtk::ListStore::create(treeModel);
        this->set_model(listStore);
        this->pack_start(treeModel.displayName);
    }

    ComboBoxExtra() : Gtk::ComboBox() {
        auto listStore = Gtk::ListStore::create(treeModel);
        this->set_model(listStore);
        this->pack_start(treeModel.displayName);
    }

    void append(const Glib::ustring &displayName, T extraData) {
        auto iter = static_cast<Gtk::ListStore *>(this->get_model().get())->append();
        auto row = *iter;
        row[treeModel.displayName] = displayName;
        row[treeModel.extraData] = extraData;
    }

    T get_active_extra_data() {
        auto iter = this->get_active();
        auto row = *iter;

        return row[treeModel.extraData];
    }

    void removeAllChildren() {
        auto listStore = dynamic_cast<Gtk::ListStore *>(this->get_model().get());
        listStore->clear();
    }
};

Com estas duas classes podemos criar combo box com dados extras de maneira muito simples:

MyCustomData *someCustomClassA, *someCustomClassB;

ComboBoxExtra<MyCustomData*> myCombo;

myCombo.append("Nome para exibir", someCustomClassA);
myCombo.append("Outro nome", someCustomClassB);

// Durante a chamada de signal_changed()
MyCustomData* extraData = myCombo.get_active_extra_data();

Agora podemos pegar os ponteiros das classes que estamos alterando e atualizar diretamente, sem loops e sem maluquices.