Search code examples
c++gtkmm

How can I programmatically add a tab to a GTKmm notebook in C++?


I'm working on a GTKmm application(a simple text editor, as an exercise), in which I have a notebook to which I want to add a tab. The notebook shows, but the newly added tab doesn't. I'm doing it in the following way:

void MainWindow::AddTabToNotebook()
{
    Gtk::Box box;

    notebook->append_page(box);

    notebook->show_all();
}

MainWindow is a class that inherits Gtk::Window and contains a pointer to Gtk::Notebook which is loaded from a Glade file using Gtk::Builder. Whenever I click the button that calls the function I get the following message in the terminal: Gtk-CRITICAL **: gtk_notebook_get_tab_label: assertion 'list != NULL' failed. Any help is appreciated.

MainWindow.h:

#ifndef MAIN_WINDOW_H
#define MAIN_WINDOW_H

#include <gtkmm.h>

class MainWindow : public Gtk::Window
{
protected:
    
    Gtk::Button* buttonOpenFile;
    Gtk::Button* buttonSave;
    Gtk::Button* buttonSaveAs;
    
    Gtk::Button *dialogButonOpen;
    Gtk::TextView *text;
    Gtk::MenuButton *buttonMenu;
    Gtk::Notebook *notebook;

    Gtk::FileChooserDialog *openFileDialog;

    Glib::RefPtr<Gtk::Builder> builder;
    void AddTabToNotebook();
    void OnButtonOpenFileClick();
    void OnButtonSaveClick();
    void OnButtonSaveAsClick();
    void OnFileChosen();
    void OnDialogButtonOpenClick();

public:
    MainWindow(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade);
    ~MainWindow();
};
#endif

MainWindow.cpp:


#include"MainWindow.h"
#include<iostream>
#include<gtkmm.h>

MainWindow::MainWindow(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade)
    :Gtk::Window(cobject),
    builder(refGlade)
{
    builder->get_widget("buttonOpenFile", buttonOpenFile);
    builder->get_widget("buttonSave", buttonSave);
    builder->get_widget("buttonSaveAs", buttonSaveAs);
    builder->get_widget("buttonMenu", buttonMenu);
    builder->get_widget("openFileDialog", openFileDialog);
    builder->get_widget("dialogButtonOpen", dialogButonOpen);
    builder->get_widget("notebook", notebook);

    buttonOpenFile->signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::OnButtonOpenFileClick));
    dialogButonOpen->signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::OnDialogButtonOpenClick));
    buttonSave->signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::AddTabToNotebook));

    show_all_children();
}
MainWindow::~MainWindow()
{}
void MainWindow::OnButtonOpenFileClick()
{
    if(openFileDialog)
    {
        openFileDialog->show();
    }
}
void MainWindow::OnDialogButtonOpenClick()
{
    auto file=openFileDialog->get_file();
    std::cout<<file->get_path();

    if(openFileDialog)
    {
        openFileDialog->close();
    }
}
void MainWindow::AddTabToNotebook()
{
    Gtk::Box box;
    
    notebook->append_page(box);

    notebook->show_all();
    
}

The glade file:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
  <requires lib="gtk+" version="3.24"/>
  <object class="GtkWindow" id="MainWindow">
    <property name="name">MainWindow</property>
    <property name="width-request">800</property>
    <property name="height-request">600</property>
    <property name="can-focus">False</property>
    <child>
      <object class="GtkViewport">
        <property name="visible">True</property>
        <property name="can-focus">False</property>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can-focus">False</property>
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkBox">
                <property name="height-request">30</property>
                <property name="visible">True</property>
                <property name="can-focus">False</property>
                <property name="valign">start</property>
                <property name="hexpand">True</property>
                <child>
                  <object class="GtkButton" id="buttonOpenFile">
                    <property name="label" translatable="yes">Open file</property>
                    <property name="name">buttonOpenFile</property>
                    <property name="visible">True</property>
                    <property name="can-focus">True</property>
                    <property name="receives-default">True</property>
                    <property name="tooltip-text" translatable="yes">Open a file</property>
                    <signal name="clicked" handler="OnButtonOpenFileClicked" swapped="no"/>
                  </object>
                  <packing>
                    <property name="expand">False</property>
                    <property name="fill">True</property>
                    <property name="position">0</property>
                  </packing>
                </child>
                <child>
                  <object class="GtkButton" id="buttonSave">
                    <property name="label" translatable="yes">Save changes</property>
                    <property name="name">buttonSave</property>
                    <property name="visible">True</property>
                    <property name="can-focus">True</property>
                    <property name="receives-default">True</property>
                    <property name="tooltip-text" translatable="yes">Save changes to the current document</property>
                    <signal name="clicked" handler="OnButtonSaveChangesClicked" swapped="no"/>
                  </object>
                  <packing>
                    <property name="expand">False</property>
                    <property name="fill">True</property>
                    <property name="position">1</property>
                  </packing>
                </child>
                <child>
                  <object class="GtkButton" id="buttonSaveAs">
                    <property name="label" translatable="yes">Save As...</property>
                    <property name="name">buttonSaveAs</property>
                    <property name="visible">True</property>
                    <property name="can-focus">True</property>
                    <property name="receives-default">True</property>
                    <signal name="clicked" handler="OnButtonSaveAsClicked" swapped="no"/>
                  </object>
                  <packing>
                    <property name="expand">False</property>
                    <property name="fill">True</property>
                    <property name="position">2</property>
                  </packing>
                </child>
                <child>
                  <object class="GtkMenuButton" id="buttonMenu">
                    <property name="name">buttonMenu</property>
                    <property name="visible">True</property>
                    <property name="can-focus">True</property>
                    <property name="focus-on-click">False</property>
                    <property name="receives-default">True</property>
                    <property name="use-popover">False</property>
                    <signal name="toggled" handler="OnButtonMenuToggled" swapped="no"/>
                    <child>
                      <placeholder/>
                    </child>
                  </object>
                  <packing>
                    <property name="expand">False</property>
                    <property name="fill">True</property>
                    <property name="pack-type">end</property>
                    <property name="position">3</property>
                  </packing>
                </child>
                <child>
                  <placeholder/>
                </child>
                <child>
                  <placeholder/>
                </child>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkViewport">
                <property name="height-request">780</property>
                <property name="visible">True</property>
                <property name="can-focus">False</property>
                <child>
                  <object class="GtkScrolledWindow">
                    <property name="visible">True</property>
                    <property name="can-focus">True</property>
                    <property name="shadow-type">in</property>
                    <child>
                      <object class="GtkViewport">
                        <property name="visible">True</property>
                        <property name="can-focus">False</property>
                        <child>
                          <object class="GtkNotebook" id="notebook">
                            <property name="name">notebook</property>
                            <property name="width-request">200</property>
                            <property name="height-request">200</property>
                            <property name="visible">True</property>
                            <property name="can-focus">True</property>
                            <child>
                              <object class="GtkTextView">
                                <property name="visible">True</property>
                                <property name="can-focus">True</property>
                              </object>
                            </child>
                            <child type="tab">
                              <object class="GtkLabel">
                                <property name="visible">True</property>
                                <property name="can-focus">False</property>
                                <property name="label" translatable="yes">page 1</property>
                              </object>
                              <packing>
                                <property name="tab-fill">False</property>
                              </packing>
                            </child>
                            <child>
                              <placeholder/>
                            </child>
                            <child type="tab">
                              <placeholder/>
                            </child>
                          </object>
                        </child>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
            <child>
              <placeholder/>
            </child>
          </object>
        </child>
      </object>
    </child>
  </object>
  <object class="GtkFileChooserDialog" id="openFileDialog">
    <property name="name">openFileDialog</property>
    <property name="width-request">800</property>
    <property name="height-request">600</property>
    <property name="can-focus">False</property>
    <property name="type-hint">dialog</property>
    <child internal-child="vbox">
      <object class="GtkBox">
        <property name="width-request">800</property>
        <property name="height-request">600</property>
        <property name="can-focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">2</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox">
            <property name="can-focus">False</property>
            <property name="layout-style">end</property>
            <child>
              <object class="GtkButton" id="dialogButtonOpen">
                <property name="label" translatable="yes">Open</property>
                <property name="name">dialogButtonOpen</property>
                <property name="visible">True</property>
                <property name="can-focus">True</property>
                <property name="receives-default">True</property>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

Whatever I try to append to the notebook, be it a Gtk::Box or Gtk:TextView, the new tab doesn't show.

Note: This is a work in progress, so the save button's clicked signal is responsible for adding a new tab(blank document)to the notebook.


Solution

  • The problem is that:

    1. The Gtk::Notebook does not take ownership of the widget you are adding.
    2. The widget you are adding is local to your callback (and hence destroyed when leaving it).

    I was able to make your example work by adding a widget (here a Gtk::Label) attribute to your window (so it outlives the callback):

    class MainWindow : public Gtk::Window
    {
    protected:
        
        Gtk::Button* buttonOpenFile;
        Gtk::Button* buttonSave;
        Gtk::Button* buttonSaveAs;
        Gtk::Label   label;        // <-- See here
        
        Gtk::Button *dialogButonOpen;
    
        ...
    

    and then show()ing it explicitly in the callback:

    void MainWindow::AddTabToNotebook()
    {
        notebook->append_page(label);
    
        label.set_text("test");
        label.show();
    }
    

    on Ubuntu and Gtkmm 3.24.20. You can achieve the same thing by using the Glade file, I leave this part to you.