Example

20.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.

Slika 20-1Printing - Simple

Source Code

File: examplewindow.h (For use with gtkmm 3, not gtkmm 2)

#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H

#include <gtkmm.h>

class PrintFormOperation;

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow();
  virtual ~ExampleWindow();

protected:

  void build_main_menu();

  void print_or_preview(Gtk::PrintOperationAction 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(const Glib::RefPtr<PrintFormOperation>& operation);

  void on_printoperation_done(Gtk::PrintOperationResult result, const Glib::RefPtr<PrintFormOperation>& operation);

  //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;


  //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;
};

#endif //GTKMM_EXAMPLEWINDOW_H

File: printformoperation.h (For use with gtkmm 3, not gtkmm 2)

#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:
  virtual void on_begin_print(const Glib::RefPtr<Gtk::PrintContext>& context);
  virtual void on_draw_page(const Glib::RefPtr<Gtk::PrintContext>& context, int page_nr);

  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 3, not gtkmm 2)

#include "examplewindow.h"
#include "printformoperation.h"

#include <iostream>

#include <pangomm.h>

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

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

  add(m_VBox);

  build_main_menu();

  m_VBox.pack_start(m_Grid);

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

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

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

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

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

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

  m_VBox.pack_start(m_Statusbar);

  show_all_children();
}

ExampleWindow::~ExampleWindow()
{
}

void ExampleWindow::build_main_menu()
{
  //Create actions for menus and toolbars:
  Glib::RefPtr<Gio::SimpleActionGroup> 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);

  m_refBuilder = Gtk::Builder::create();
 
  //TODO: add_accel_group(m_refBuilder->get_accel_group());

  //Layout the actions in a menubar and toolbar:
  
  Glib::ustring ui_info = 
   "<interface>"
    "  <menu id='menu-example'>"
    "    <submenu>"
    "      <attribute name='label' translatable='yes'>_File</attribute>"
    "      <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>"
    "    </submenu>"
    "  </menu>"
/* TODO:
      "  <toolbar  name='ToolBar'>"
        "    <toolitem action='New'/>"
        "    <toolitem action='Print'/>"
        "      <separator/>"
        "    <toolitem action='Quit'/>"
        "  </toolbar>"
*/
    "</interface>";

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


  //Get the menubar and toolbar widgets, and add them to a container widget:
  Glib::RefPtr<Glib::Object> object =
    m_refBuilder->get_object("menu-example");
  Glib::RefPtr<Gio::Menu> gmenu =
    Glib::RefPtr<Gio::Menu>::cast_dynamic(object);
  if(!gmenu)
    g_warning("GMenu not found");

  Gtk::MenuBar* pMenubar = new Gtk::MenuBar(gmenu);
  m_VBox.pack_start(*pMenubar, Gtk::PACK_SHRINK);

/* TODO:
  Gtk::Widget* pToolbar = m_refBuilder->get_widget("/ToolBar") ;
  if(pToolbar)
    m_VBox.pack_start(*pToolbar, Gtk::PACK_SHRINK);
*/
}

void ExampleWindow::on_printoperation_status_changed(
        const Glib::RefPtr<PrintFormOperation>& operation)
{
  Glib::ustring status_msg;

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

  m_Statusbar.push(status_msg, m_ContextId);
}

void ExampleWindow::on_printoperation_done(Gtk::PrintOperationResult result,
        const Glib::RefPtr<PrintFormOperation>& operation)
{
  //Printing is "done" when the print data is spooled.

  if (result == Gtk::PRINT_OPERATION_RESULT_ERROR)
  {
    Gtk::MessageDialog err_dialog(*this, "Error printing form", false,
            Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
    err_dialog.run();
  }
  else if (result == Gtk::PRINT_OPERATION_RESULT_APPLY)
  {
    //Update PrintSettings with the ones used in this PrintOperation:
    m_refSettings = operation->get_print_settings();
  }

  if (! operation->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.
    operation->signal_status_changed().connect(sigc::bind(sigc::mem_fun(*this,
                    &ExampleWindow::on_printoperation_status_changed),
                operation));
  }
}

void ExampleWindow::print_or_preview(Gtk::PrintOperationAction print_action)
{
  //Create a new PrintOperation with our PageSetup and PrintSettings:
  //(We use our derived PrintOperation class)
  Glib::RefPtr<PrintFormOperation> print = PrintFormOperation::create();

  print->set_name(m_NameEntry.get_text() + " " + m_SurnameEntry.get_text());
  print->set_comments(m_refTextBuffer->get_text(false /*Don't include hidden*/));

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

  print->signal_done().connect(sigc::bind(sigc::mem_fun(*this,
                  &ExampleWindow::on_printoperation_done), print));
  try
  {
    print->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:
  Glib::RefPtr<Gtk::PageSetup> 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::PRINT_OPERATION_ACTION_PREVIEW);
}

void ExampleWindow::on_menu_file_print()
{
  print_or_preview(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG);
}

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

File: main.cc (For use with gtkmm 3, not gtkmm 2)

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

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

  ExampleWindow window;

  //Shows the window and returns when it is closed.
  return app->run(window);
}

File: printformoperation.cc (For use with gtkmm 3, not gtkmm 2)

#include "printformoperation.h"

PrintFormOperation::PrintFormOperation()
{
}

PrintFormOperation::~PrintFormOperation()
{
}

Glib::RefPtr<PrintFormOperation> PrintFormOperation::create()
{
  return Glib::RefPtr<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:
  Pango::LayoutIter iter = m_refLayout->get_iter();

  double start_pos = 0;
  int line_index = 0;

  do
  {
    if(line_index >= start_page_line)
    {
      Glib::RefPtr<Pango::LayoutLine> layout_line = iter.get_line();
      Pango::Rectangle 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());
}