Example

19.8.1. Simple

The following example demonstrates how to print some input from a user interface. 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 19-1Printing - 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>
#include <memory>

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;

  unsigned m_ContextId;
  Gtk::Statusbar m_Statusbar;

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

  std::unique_ptr<Gtk::MessageDialog> m_pDialog;
};

#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: 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: examplewindow.cc (For use with gtkmm 4)

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

const Glib::ustring app_title = "gtkmm Printing Example";

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();

  m_ContextId = m_Statusbar.get_context_id(app_title);

  set_title(app_title);
  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_expand(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.push(status_msg, m_ContextId);
}

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_pDialog)
    {
      m_pDialog.reset(new Gtk::MessageDialog(*this, "Error printing form",
        false /* use_markup */, Gtk::MessageType::ERROR, Gtk::ButtonsType::OK,
        true /* modal */));
      m_pDialog->set_hide_on_close(true);
      m_pDialog->signal_response().connect(
        sigc::hide(sigc::mem_fun(*m_pDialog, &Gtk::Widget::hide)));
    }
    m_pDialog->show();
  }
  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()
{
  hide();
}

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());
}