Examples
| Chapter 21. Printing |

## Examples

### Simple

The following example demonstrates how to print some input from a user interface using `PrintOperation`. It shows how to implement `on_begin_print` and `on_draw_page`, as well as how to track print status and update the print settings.

Figure 21.1. Printing - Simple

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    // This file is part of the printing/simple and printing/advanced examples

    #include <gtkmm.h>

    class PrintFormOperation;

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow(const Glib::RefPtr<Gtk::Application>& app);
      virtual ~ExampleWindow();

    protected:

      void build_main_menu(const Glib::RefPtr<Gtk::Application>& app);

      void print_or_preview(Gtk::PrintOperation::Action print_action);

      //PrintOperation signal handlers.
      //We handle these so can get necessary information to update the UI or print settings.
      //Our derived PrintOperation class also overrides some default signal handlers.
      void on_printoperation_status_changed();
      void on_printoperation_done(Gtk::PrintOperation::Result result);

      //Action signal handlers:
      void on_menu_file_new();
      void on_menu_file_page_setup();
      void on_menu_file_print_preview();
      void on_menu_file_print();
      void on_menu_file_quit();

      //Printing-related objects:
      Glib::RefPtr<Gtk::PageSetup> m_refPageSetup;
      Glib::RefPtr<Gtk::PrintSettings> m_refSettings;
      Glib::RefPtr<PrintFormOperation> m_refPrintFormOperation;

      //Child widgets:
      Gtk::Box m_VBox;
      Gtk::Grid m_Grid;

      Gtk::Label m_NameLabel;
      Gtk::Entry m_NameEntry;

      Gtk::Label m_SurnameLabel;
      Gtk::Entry m_SurnameEntry;

      Gtk::Label m_CommentsLabel;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TextView m_TextView;

      Glib::RefPtr<Gtk::TextBuffer> m_refTextBuffer;

      Gtk::Label m_Statusbar;

      Glib::RefPtr<Gtk::Builder> m_refBuilder;

      Glib::RefPtr<Gtk::AlertDialog> m_refDialog;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `printformoperation.h` (For use with gtkmm 4)

    #ifndef GTKMM_PRINT_FORM_OPERATION_H
    #define GTKMM_PRINT_FORM_OPERATION_H

    #include <gtkmm.h>
    #include <pangomm.h>
    #include <vector>

    //We derive our own class from PrintOperation,
    //so we can put the actual print implementation here.
    class PrintFormOperation : public Gtk::PrintOperation
    {
     public:
      static Glib::RefPtr<PrintFormOperation> create();
      virtual ~PrintFormOperation();

      void set_name(const Glib::ustring& name) { m_Name = name; }
      void set_comments(const Glib::ustring& comments) { m_Comments = comments; }

     protected:
      PrintFormOperation();

      //PrintOperation default signal handler overrides:
      void on_begin_print(const Glib::RefPtr<Gtk::PrintContext>& context) override;
      void on_draw_page(const Glib::RefPtr<Gtk::PrintContext>& context, int page_nr) override;

      Glib::ustring m_Name;
      Glib::ustring m_Comments;
      Glib::RefPtr<Pango::Layout> m_refLayout;
      std::vector<int> m_PageBreaks; // line numbers where a page break occurs
    };

    #endif // GTKMM_PRINT_FORM_OPERATION_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include "printformoperation.h"
    #include <iostream>

    ExampleWindow::ExampleWindow(const Glib::RefPtr<Gtk::Application>& app)
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_NameLabel("Name"),
      m_SurnameLabel("Surname"),
      m_CommentsLabel("Comments")
    {
      m_refPageSetup = Gtk::PageSetup::create();
      m_refSettings = Gtk::PrintSettings::create();

      set_title("gtkmm Printing Example");
      set_default_size(400, 300);

      set_child(m_VBox);

      build_main_menu(app);

      m_VBox.append(m_Grid);

      //Arrange the widgets inside the grid:
      m_Grid.set_expand(true);
      m_Grid.set_row_spacing(5);
      m_Grid.set_column_spacing(5);
      m_Grid.attach(m_NameLabel, 0, 0);
      m_Grid.attach(m_NameEntry, 1, 0);

      m_Grid.attach(m_SurnameLabel, 0, 1);
      m_Grid.attach(m_SurnameEntry, 1, 1);

      //Add the TextView, inside a ScrolledWindow:
      m_ScrolledWindow.set_child(m_TextView);

      //Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);

      m_Grid.attach(m_CommentsLabel, 0, 2);
      m_Grid.attach(m_ScrolledWindow, 1, 2);
      m_ScrolledWindow.set_expand(true);

      m_refTextBuffer = Gtk::TextBuffer::create();
      m_TextView.set_buffer(m_refTextBuffer);

      m_Statusbar.set_hexpand(true);
      m_VBox.append(m_Statusbar);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::build_main_menu(const Glib::RefPtr<Gtk::Application>& app)
    {
      //Create actions for menus and toolbars:
      auto refActionGroup = Gio::SimpleActionGroup::create();

      //File menu:
      refActionGroup->add_action("new",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_new));

      refActionGroup->add_action("pagesetup",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_page_setup));

      refActionGroup->add_action("printpreview",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_print_preview));

      refActionGroup->add_action("print",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_print));

      refActionGroup->add_action("quit",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_quit));

      insert_action_group("example", refActionGroup);

      // When the menubar is a child of a Gtk::Window, keyboard accelerators are not
      // automatically fetched from the Gio::Menu.
      // See the examples/book/menus/main_menu example for an alternative way of
      // adding the menubar when using Gtk::ApplicationWindow.
      app->set_accel_for_action("example.new", "<Primary>n");
      app->set_accel_for_action("example.print", "<Primary>p");
      app->set_accel_for_action("example.quit", "<Primary>q");

      m_refBuilder = Gtk::Builder::create();

      // Layout the actions in a menubar:
      Glib::ustring ui_menu_info =
        "<interface>"
        "  <menu id='menu-example'>"
        "    <submenu>"
        "      <attribute name='label' translatable='yes'>_File</attribute>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_New</attribute>"
        "          <attribute name='action'>example.new</attribute>"
        "        </item>"
        "      </section>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>Page _Setup...</attribute>"
        "          <attribute name='action'>example.pagesetup</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>Print Preview</attribute>"
        "          <attribute name='action'>example.printpreview</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Print...</attribute>"
        "          <attribute name='action'>example.print</attribute>"
        "        </item>"
        "      </section>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Quit</attribute>"
        "          <attribute name='action'>example.quit</attribute>"
        "        </item>"
        "      </section>"
        "    </submenu>"
        "  </menu>"
        "</interface>";

      try
      {
        m_refBuilder->add_from_string(ui_menu_info);
      }
      catch(const Glib::Error& ex)
      {
        std::cerr << "building menus failed: " << ex.what();
      }

      // Layout the actions in a toolbar:
      Glib::ustring ui_toolbar_info =
        "<!-- Generated with glade 3.18.3 and then changed manually -->"
        "<interface>"
          "<object class='GtkBox' id='toolbar'>"
            "<property name='can_focus'>False</property>"
            "<property name='spacing'>3</property>"
            "<child>"
              "<object class='GtkButton' id='toolbutton_new'>"
                "<property name='can_focus'>False</property>"
                "<property name='tooltip_text' translatable='yes'>New</property>"
                "<property name='action_name'>example.new</property>"
                "<property name='icon_name'>document-new</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
            "<child>"
              "<object class='GtkButton' id='toolbutton_print'>"
                "<property name='can_focus'>False</property>"
                "<property name='tooltip_text' translatable='yes'>Print</property>"
                "<property name='action_name'>example.print</property>"
                "<property name='icon_name'>document-print</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
            "<child>"
              "<object class='GtkSeparator' id='separator1'>"
                "<property name='can_focus'>False</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
            "<child>"
              "<object class='GtkButton' id='toolbutton_quit'>"
                "<property name='can_focus'>False</property>"
                "<property name='tooltip_text' translatable='yes'>Quit</property>"
                "<property name='action_name'>example.quit</property>"
                "<property name='icon_name'>application-exit</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
          "</object>"
        "</interface>";

      try
      {
        m_refBuilder->add_from_string(ui_toolbar_info);
      }
      catch(const Glib::Error& ex)
      {
        std::cerr << "building toolbar failed: " << ex.what();
      }

      // Get the menubar and add it to a container widget:
      auto object = m_refBuilder->get_object("menu-example");
      auto gmenu = std::dynamic_pointer_cast<Gio::Menu>(object);
      if (!gmenu)
        g_warning("GMenu not found");
      else
      {
        auto pMenuBar = Gtk::make_managed<Gtk::PopoverMenuBar>(gmenu);

        // Add the PopoverMenuBar to the window:
        m_VBox.append(*pMenuBar);
      }

      // Get the toolbar and add it to a container widget:
      auto toolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
      if (!toolbar)
        g_warning("toolbar not found");
      else
        m_VBox.append(*toolbar);
    }

    void ExampleWindow::on_printoperation_status_changed()
    {
      Glib::ustring status_msg;

      if (m_refPrintFormOperation->is_finished())
      {
        status_msg = "Print job completed.";
      }
      else
      {
        //You could also use get_status().
        status_msg = m_refPrintFormOperation->get_status_string();
      }

      m_Statusbar.set_text(status_msg);
    }

    void ExampleWindow::on_printoperation_done(Gtk::PrintOperation::Result result)
    {
      //Printing is "done" when the print data is spooled.

      if (result == Gtk::PrintOperation::Result::ERROR)
      {
        if (!m_refDialog)
          m_refDialog = Gtk::AlertDialog::create("Error printing form");
        m_refDialog->show(*this);
      }
      else if (result == Gtk::PrintOperation::Result::APPLY)
      {
        //Update PrintSettings with the ones used in this PrintOperation:
        m_refSettings = m_refPrintFormOperation->get_print_settings();
      }

      if (!m_refPrintFormOperation->is_finished())
      {
        //We will connect to the status-changed signal to track status
        //and update a status bar. In addition, you can, for example,
        //keep a list of active print operations, or provide a progress dialog.
        m_refPrintFormOperation->signal_status_changed().connect(sigc::mem_fun(*this,
                        &ExampleWindow::on_printoperation_status_changed));
      }
    }

    void ExampleWindow::print_or_preview(Gtk::PrintOperation::Action print_action)
    {
      //Create a new PrintOperation with our PageSetup and PrintSettings:
      //(We use our derived PrintOperation class)
      m_refPrintFormOperation = PrintFormOperation::create();

      m_refPrintFormOperation->set_name(m_NameEntry.get_text() + " " + m_SurnameEntry.get_text());
      m_refPrintFormOperation->set_comments(m_refTextBuffer->get_text(false /*Don't include hidden*/));
      // In the printing/advanced example, the font will be set through a custom tab
      // in the print dialog.

      m_refPrintFormOperation->set_track_print_status();
      m_refPrintFormOperation->set_default_page_setup(m_refPageSetup);
      m_refPrintFormOperation->set_print_settings(m_refSettings);

      m_refPrintFormOperation->signal_done().connect(sigc::mem_fun(*this,
                      &ExampleWindow::on_printoperation_done));
      try
      {
        m_refPrintFormOperation->run(print_action /* print or preview */, *this);
      }
      catch (const Gtk::PrintError& ex)
      {
        //See documentation for exact Gtk::PrintError error codes.
        std::cerr << "An error occurred while trying to run a print operation:"
            << ex.what() << std::endl;
      }
    }

    void ExampleWindow::on_menu_file_new()
    {
      //Clear entries and textview:
      m_NameEntry.set_text("");
      m_SurnameEntry.set_text("");
      m_refTextBuffer->set_text("");
      m_TextView.set_buffer(m_refTextBuffer);
    }

    void ExampleWindow::on_menu_file_page_setup()
    {
      //Show the page setup dialog, asking it to start with the existing settings:
      auto new_page_setup =
          Gtk::run_page_setup_dialog(*this, m_refPageSetup, m_refSettings);

      //Save the chosen page setup dialog for use when printing, previewing, or
      //showing the page setup dialog again:
      m_refPageSetup = new_page_setup;
    }

    void ExampleWindow::on_menu_file_print_preview()
    {
      print_or_preview(Gtk::PrintOperation::Action::PREVIEW);
    }

    void ExampleWindow::on_menu_file_print()
    {
      print_or_preview(Gtk::PrintOperation::Action::PRINT_DIALOG);
    }

    void ExampleWindow::on_menu_file_quit()
    {
      close();
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv, app);
    }

File: `printformoperation.cc` (For use with gtkmm 4)

    #include "printformoperation.h"

    PrintFormOperation::PrintFormOperation()
    {
    }

    PrintFormOperation::~PrintFormOperation()
    {
    }

    Glib::RefPtr<PrintFormOperation> PrintFormOperation::create()
    {
      return Glib::make_refptr_for_instance<PrintFormOperation>(new PrintFormOperation());
    }

    void PrintFormOperation::on_begin_print(
            const Glib::RefPtr<Gtk::PrintContext>& print_context)
    {
      //Create and set up a Pango layout for PrintData based on the passed
      //PrintContext: We then use this to calculate the number of pages needed, and
      //the lines that are on each page.
      m_refLayout = print_context->create_pango_layout();

      Pango::FontDescription font_desc("sans 12");
      m_refLayout->set_font_description(font_desc);

      const double width = print_context->get_width();
      const double height = print_context->get_height();

      m_refLayout->set_width(static_cast<int>(width * Pango::SCALE));

      //Set and mark up the text to print:
      Glib::ustring marked_up_form_text;
      marked_up_form_text += "<b>Name</b>: " + m_Name + "\n\n";
      marked_up_form_text += "<b>Comments</b>: " + m_Comments;

      m_refLayout->set_markup(marked_up_form_text);

      //Set the number of pages to print by determining the line numbers
      //where page breaks occur:
      const int line_count = m_refLayout->get_line_count();

      Glib::RefPtr<Pango::LayoutLine> layout_line;
      double page_height = 0;

      for (int line = 0; line < line_count; ++line)
      {
        Pango::Rectangle ink_rect, logical_rect;

        layout_line = m_refLayout->get_line(line);
        layout_line->get_extents(ink_rect, logical_rect);

        const double line_height = logical_rect.get_height() / 1024.0;

        if (page_height + line_height > height)
        {
          m_PageBreaks.push_back(line);
          page_height = 0;
        }

        page_height += line_height;
      }

      set_n_pages(m_PageBreaks.size() + 1);
    }

    void PrintFormOperation::on_draw_page(
            const Glib::RefPtr<Gtk::PrintContext>& print_context, int page_nr)
    {
      //Decide which lines we need to print in order to print the specified page:
      int start_page_line = 0;
      int end_page_line = 0;

      if(page_nr == 0)
      {
        start_page_line = 0;
      }
      else
      {
        start_page_line = m_PageBreaks[page_nr - 1];
      }

      if(page_nr < static_cast<int>(m_PageBreaks.size()))
      {
        end_page_line = m_PageBreaks[page_nr];
      }
      else
      {
        end_page_line = m_refLayout->get_line_count();
      }

      //Get a Cairo Context, which is used as a drawing board:
      Cairo::RefPtr<Cairo::Context> cairo_ctx = print_context->get_cairo_context();

      //We'll use black letters:
      cairo_ctx->set_source_rgb(0, 0, 0);

      //Render Pango LayoutLines over the Cairo context:
      auto iter = m_refLayout->get_iter();

      double start_pos = 0;
      int line_index = 0;

      do
      {
        if(line_index >= start_page_line)
        {
          auto layout_line = iter.get_line();
          auto logical_rect = iter.get_line_logical_extents();
          int baseline = iter.get_baseline();

          if (line_index == start_page_line)
          {
            start_pos = logical_rect.get_y() / 1024.0;
          }

          cairo_ctx->move_to(logical_rect.get_x() / 1024.0,
            baseline / 1024.0 - start_pos);

          layout_line->show_in_cairo_context(cairo_ctx);
        }

        line_index++;
      }
      while(line_index < end_page_line && iter.next_line());
    }

### PrintDialog

The following example demonstrates how to print some input from a user interface using `PrintDialog`. The user interface is similar to the previous example.

Figure 21.2. Printing - PrintDialog

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class PrintFormDialog;

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow(const Glib::RefPtr<Gtk::Application>& app);
      ~ExampleWindow() override;

    protected:
      void build_main_menu(const Glib::RefPtr<Gtk::Application>& app);

      // Action signal handlers:
      void on_menu_file_new();
      void on_menu_file_print_setup();
      void on_menu_file_print();
      void on_menu_file_quit();

      // Printing-related objects:
      Glib::RefPtr<PrintFormDialog> m_refPrintFormDialog;

      // Child widgets:
      Gtk::Box m_VBox;
      Gtk::Grid m_Grid;

      Gtk::Label m_NameLabel;
      Gtk::Entry m_NameEntry;

      Gtk::Label m_SurnameLabel;
      Gtk::Entry m_SurnameEntry;

      Gtk::Label m_CommentsLabel;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TextView m_TextView;

      Glib::RefPtr<Gtk::TextBuffer> m_refTextBuffer;

      Glib::RefPtr<Gtk::Builder> m_refBuilder;
    };
    #endif //GTKMM_EXAMPLEWINDOW_H

File: `printformdialog.h` (For use with gtkmm 4)

    #ifndef GTKMM_PRINT_FORM_DIALOG_H
    #define GTKMM_PRINT_FORM_DIALOG_H

    #include <gtkmm.h>
    #include <pangomm.h>
    #include <vector>

    #define HAS_PRINT_DIALOG GTKMM_CHECK_VERSION(4,13,1)

    #if HAS_PRINT_DIALOG
    // We derive our own class from PrintDialog,
    // so we can put the actual print implementation here.
    class PrintFormDialog : public Gtk::PrintDialog
    {
    public:
      static Glib::RefPtr<PrintFormDialog> create();
      virtual ~PrintFormDialog();

      void set_name(const Glib::ustring& name) { m_Name = name; }
      void set_comments(const Glib::ustring& comments) { m_Comments = comments; }
      void do_setup(Gtk::Window* parent);
      void do_print(Gtk::Window* parent);

    protected:
      PrintFormDialog();

      // PrintDialog callback slots:
      void on_setup_finish(const Glib::RefPtr<Gio::AsyncResult>& result);
      void on_print_finish(const Glib::RefPtr<Gio::AsyncResult>& result);
      Cairo::ErrorStatus write_func(const unsigned char* data, unsigned int length,
        Glib::RefPtr<Gio::OutputStream>& stream);
      void do_printing(const Cairo::RefPtr<Cairo::Context>& cairo_ctx);

      Glib::ustring m_Name;
      Glib::ustring m_Comments;
      Glib::RefPtr<Pango::Layout> m_refLayout;
      Glib::RefPtr<Gtk::PrintSetup> m_refPrintSetup;
    };
    #endif // HAS_PRINT_DIALOG

    #endif // GTKMM_PRINT_FORM_DIALOG_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include "printformdialog.h"
    #include <iostream>

    ExampleWindow::ExampleWindow(const Glib::RefPtr<Gtk::Application>& app)
    : m_VBox(Gtk::Orientation::VERTICAL),
      m_NameLabel("Name"),
      m_SurnameLabel("Surname"),
      m_CommentsLabel("Comments")
    {
      set_title("gtkmm PrintDialog Example");
      set_default_size(400, 300);

    #if HAS_PRINT_DIALOG
      // Create a PrintDialog. (We use our derived PrintDialog class.)
      m_refPrintFormDialog = PrintFormDialog::create();
    #endif // HAS_PRINT_DIALOG

      set_child(m_VBox);

      build_main_menu(app);

      m_VBox.append(m_Grid);

      // Arrange the widgets inside the grid:
      m_Grid.set_expand(true);
      m_Grid.set_row_spacing(5);
      m_Grid.set_column_spacing(5);
      m_Grid.attach(m_NameLabel, 0, 0);
      m_Grid.attach(m_NameEntry, 1, 0);

      m_Grid.attach(m_SurnameLabel, 0, 1);
      m_Grid.attach(m_SurnameEntry, 1, 1);

      // Add the TextView, inside a ScrolledWindow:
      m_ScrolledWindow.set_child(m_TextView);

      // Only show the scrollbars when they are necessary:
      m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);

      m_Grid.attach(m_CommentsLabel, 0, 2);
      m_Grid.attach(m_ScrolledWindow, 1, 2);
      m_ScrolledWindow.set_expand(true);

      m_refTextBuffer = Gtk::TextBuffer::create();
      m_TextView.set_buffer(m_refTextBuffer);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::build_main_menu(const Glib::RefPtr<Gtk::Application>& app)
    {
      // Create actions for menus and toolbars:
      auto refActionGroup = Gio::SimpleActionGroup::create();

      // File menu:
      refActionGroup->add_action("new",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_new));

      refActionGroup->add_action("printsetup",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_print_setup));

      refActionGroup->add_action("print",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_print));

      refActionGroup->add_action("quit",
        sigc::mem_fun(*this, &ExampleWindow::on_menu_file_quit));

      insert_action_group("example", refActionGroup);

      // When the menubar is a child of a Gtk::Window, keyboard accelerators are not
      // automatically fetched from the Gio::Menu.
      // See the examples/book/menus/main_menu example for an alternative way of
      // adding the menubar when using Gtk::ApplicationWindow.
      app->set_accel_for_action("example.new", "<Primary>n");
      app->set_accel_for_action("example.print", "<Primary>p");
      app->set_accel_for_action("example.quit", "<Primary>q");

      m_refBuilder = Gtk::Builder::create();

      // Layout the actions in a menubar:
      Glib::ustring ui_menu_info =
        "<interface>"
        "  <menu id='menu-example'>"
        "    <submenu>"
        "      <attribute name='label' translatable='yes'>_File</attribute>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_New</attribute>"
        "          <attribute name='action'>example.new</attribute>"
        "        </item>"
        "      </section>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>Print _Setup...</attribute>"
        "          <attribute name='action'>example.printsetup</attribute>"
        "        </item>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Print...</attribute>"
        "          <attribute name='action'>example.print</attribute>"
        "        </item>"
        "      </section>"
        "      <section>"
        "        <item>"
        "          <attribute name='label' translatable='yes'>_Quit</attribute>"
        "          <attribute name='action'>example.quit</attribute>"
        "        </item>"
        "      </section>"
        "    </submenu>"
        "  </menu>"
        "</interface>";

      try
      {
        m_refBuilder->add_from_string(ui_menu_info);
      }
      catch(const Glib::Error& ex)
      {
        std::cerr << "building menus failed: " << ex.what();
      }

      // Layout the actions in a toolbar:
      Glib::ustring ui_toolbar_info =
        "<!-- Generated with glade 3.18.3 and then changed manually -->"
        "<interface>"
          "<object class='GtkBox' id='toolbar'>"
            "<property name='can_focus'>False</property>"
            "<property name='spacing'>3</property>"
            "<child>"
              "<object class='GtkButton' id='toolbutton_new'>"
                "<property name='can_focus'>False</property>"
                "<property name='tooltip_text' translatable='yes'>New</property>"
                "<property name='action_name'>example.new</property>"
                "<property name='icon_name'>document-new</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
            "<child>"
              "<object class='GtkButton' id='toolbutton_print'>"
                "<property name='can_focus'>False</property>"
                "<property name='tooltip_text' translatable='yes'>Print</property>"
                "<property name='action_name'>example.print</property>"
                "<property name='icon_name'>document-print</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
            "<child>"
              "<object class='GtkSeparator' id='separator1'>"
                "<property name='can_focus'>False</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
            "<child>"
              "<object class='GtkButton' id='toolbutton_quit'>"
                "<property name='can_focus'>False</property>"
                "<property name='tooltip_text' translatable='yes'>Quit</property>"
                "<property name='action_name'>example.quit</property>"
                "<property name='icon_name'>application-exit</property>"
                "<property name='hexpand'>False</property>"
                "<property name='vexpand'>False</property>"
              "</object>"
            "</child>"
          "</object>"
        "</interface>";

      try
      {
        m_refBuilder->add_from_string(ui_toolbar_info);
      }
      catch(const Glib::Error& ex)
      {
        std::cerr << "building toolbar failed: " << ex.what();
      }

      // Get the menubar and add it to a container widget:
      auto object = m_refBuilder->get_object("menu-example");
      auto gmenu = std::dynamic_pointer_cast<Gio::Menu>(object);
      if (!gmenu)
        g_warning("GMenu not found");
      else
      {
        auto pMenuBar = Gtk::make_managed<Gtk::PopoverMenuBar>(gmenu);

        // Add the PopoverMenuBar to the window:
        m_VBox.append(*pMenuBar);
      }

      // Get the toolbar and add it to a container widget:
      auto toolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
      if (!toolbar)
        g_warning("toolbar not found");
      else
        m_VBox.append(*toolbar);
    }

    void ExampleWindow::on_menu_file_new()
    {
      // Clear entries and textview:
      m_NameEntry.set_text("");
      m_SurnameEntry.set_text("");
      m_refTextBuffer->set_text("");
      m_TextView.set_buffer(m_refTextBuffer);
    }

    void ExampleWindow::on_menu_file_print_setup()
    {
    #if HAS_PRINT_DIALOG
      m_refPrintFormDialog->do_setup(this);
    #else
      std::cout << "Gtk::PrintDialog is not available.\n";
    #endif // HAS_PRINT_DIALOG
    }

    void ExampleWindow::on_menu_file_print()
    {
    #if HAS_PRINT_DIALOG
      m_refPrintFormDialog->set_name(m_NameEntry.get_text() + " " + m_SurnameEntry.get_text());
      m_refPrintFormDialog->set_comments(m_refTextBuffer->get_text(false /*Don't include hidden*/));
      m_refPrintFormDialog->do_print(this);
    #else
      std::cout << "Gtk::PrintDialog is not available.\n";
    #endif // HAS_PRINT_DIALOG
    }

    void ExampleWindow::on_menu_file_quit()
    {
      close();
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char* argv[])
    {
      auto app = Gtk::Application::create("org.gtkmm.example");

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv, app);
    }

File: `printformdialog.cc` (For use with gtkmm 4)

    #include "printformdialog.h"
    #include <iostream>

    #if HAS_PRINT_DIALOG
    PrintFormDialog::PrintFormDialog()
    {
      set_page_setup(Gtk::PageSetup::create());
      set_print_settings(Gtk::PrintSettings::create());
    }

    PrintFormDialog::~PrintFormDialog()
    {
    }

    Glib::RefPtr<PrintFormDialog> PrintFormDialog::create()
    {
      return Glib::make_refptr_for_instance<PrintFormDialog>(new PrintFormDialog());
    }

    void PrintFormDialog::do_setup(Gtk::Window* parent)
    {
      set_title("Print setup");
      if (parent)
        setup(*parent, sigc::mem_fun(*this, &PrintFormDialog::on_setup_finish));
      else
        setup(sigc::mem_fun(*this, &PrintFormDialog::on_setup_finish));
    }

    void PrintFormDialog::do_print(Gtk::Window* parent)
    {
      set_title("Printing");
      if (parent)
        print(*parent, sigc::mem_fun(*this, &PrintFormDialog::on_print_finish));
      else
        print(sigc::mem_fun(*this, &PrintFormDialog::on_print_finish));
    }

    void PrintFormDialog::on_setup_finish(const Glib::RefPtr<Gio::AsyncResult>& result)
    {
      try
      {
        m_refPrintSetup = setup_finish(result);
        if (m_refPrintSetup)
        {
          set_page_setup(m_refPrintSetup->get_page_setup());
          set_print_settings(m_refPrintSetup->get_print_settings());
        }
      }
      catch (const Gtk::DialogError& err)
      {
        // Can be thrown by setup_finish(result).
        std::cout << "No setup selected. " << err.what() << std::endl;
      }
      catch (const Glib::Error& err)
      {
        std::cout << "Unexpected exception. " << err.what() << std::endl;
      }
    }

    void PrintFormDialog::on_print_finish(const Glib::RefPtr<Gio::AsyncResult>& result)
    {
      Glib::RefPtr<Gio::OutputStream> outputStream;
      try
      {
        outputStream = print_finish(result);
      }
      catch (const Gtk::DialogError& err)
      {
        // Can be thrown by print_finish(result).
        std::cout << "No output stream. " << err.what() << std::endl;
        return;
      }
      catch (const Glib::Error& err)
      {
        std::cout << "Unexpected exception. " << err.what() << std::endl;
        return;
      }
      const double paper_width = get_page_setup()->get_paper_width(Gtk::Unit::POINTS);
      const double paper_height = get_page_setup()->get_paper_height(Gtk::Unit::POINTS);
      auto cairo_surface = Cairo::PdfSurface::create_for_stream(
        sigc::bind(sigc::mem_fun(*this, &PrintFormDialog::write_func), outputStream),
        paper_width, paper_height);
      auto cairo_ctx = Cairo::Context::create(cairo_surface);

      do_printing(cairo_ctx);

      // cairo_surface->finish() calls write_func(),
      // which must be done before the stream is closed.
      cairo_surface->finish();
      try
      {
        outputStream->close();
      }
      catch (const Glib::Error& err)
      {
        std::cout << "Error closing stream. " << err.what() << std::endl;
      }
    }

    Cairo::ErrorStatus PrintFormDialog::write_func(const unsigned char* data,
      unsigned int length, Glib::RefPtr<Gio::OutputStream>& stream)
    {
      try
      {
        gsize bytes_written = 0;
        stream->write_all(data, length, bytes_written);
      }
      catch (const Glib::Error& err)
      {
        std::cout << "Error writing to stream. " << err.what() << std::endl;
        return CAIRO_STATUS_WRITE_ERROR;
      }
      return CAIRO_STATUS_SUCCESS;
    }
    void PrintFormDialog::do_printing(const Cairo::RefPtr<Cairo::Context>& cairo_ctx)
    {
      m_refLayout = Pango::Layout::create(cairo_ctx);
      m_refLayout->set_font_description(Pango::FontDescription("sans 12"));

      const auto page_setup = get_page_setup();
      const double page_width = page_setup->get_page_width(Gtk::Unit::POINTS);
      const double page_height = page_setup->get_page_height(Gtk::Unit::POINTS);
      const double left_margin = page_setup->get_left_margin(Gtk::Unit::POINTS);
      const double top_margin = page_setup->get_top_margin(Gtk::Unit::POINTS);
      m_refLayout->set_width(static_cast<int>(page_width * Pango::SCALE));
      m_refLayout->set_wrap(Pango::WrapMode::WORD_CHAR);
      cairo_ctx->translate(left_margin, top_margin);

      // Set and mark up the text to print:
      Glib::ustring marked_up_form_text =
        "<b>Name</b>: " + m_Name + "\n\n" +
        "<b>Comments</b>: " + m_Comments;
      m_refLayout->set_markup(marked_up_form_text);

      // We'll use black letters:
      cairo_ctx->set_source_rgb(0, 0, 0);

      // Render Pango LayoutLines over the Cairo context:
      const double inverse_pango_scale = 1.0 / Pango::SCALE;
      double page_y = 0.0;
      auto iter = m_refLayout->get_iter();
      double start_y_pos = iter.get_line_logical_extents().get_y() * inverse_pango_scale;
      do
      {
        const auto layout_line = iter.get_line();
        const auto logical_rect = iter.get_line_logical_extents();
        const double line_height = logical_rect.get_height() * inverse_pango_scale;

        if (page_y + line_height > page_height)
        {
          // Start a new page.
          cairo_ctx->show_page();
          page_y = 0.0;
          start_y_pos = logical_rect.get_y() * inverse_pango_scale;
        }
        page_y += line_height;

        cairo_ctx->move_to(logical_rect.get_x() * inverse_pango_scale,
          iter.get_baseline() * inverse_pango_scale - start_y_pos);
        layout_line->show_in_cairo_context(cairo_ctx);
      }
      while (iter.next_line());

      cairo_ctx->show_page();
    }
    #endif // HAS_PRINT_DIALOG

PrintDialog |  | Chapter 22. Recently Used Documents