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.
File: examplewindow.h
(For use with gtkmm 3, not gtkmm 2)
#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::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(); void on_printoperation_done(Gtk::PrintOperationResult 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; }; #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: 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 3, not gtkmm 2)
#include "examplewindow.h" #include <gtkmm/application.h> int main(int argc, char* argv[]) { auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example"); ExampleWindow window(app); // Shows the window and returns when it is closed. return app->run(window); }
File: examplewindow.cc
(For use with gtkmm 3, not gtkmm 2)
#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); add(m_VBox); build_main_menu(app); 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(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 -->" "<interface>" "<requires lib='gtk+' version='3.8'/>" "<object class='GtkToolbar' id='toolbar'>" "<property name='visible'>True</property>" "<property name='can_focus'>False</property>" "<child>" "<object class='GtkToolButton' id='toolbutton_new'>" "<property name='visible'>True</property>" "<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>" "</object>" "<packing>" "<property name='expand'>False</property>" "<property name='homogeneous'>True</property>" "</packing>" "</child>" "<child>" "<object class='GtkToolButton' id='toolbutton_print'>" "<property name='visible'>True</property>" "<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>" "</object>" "<packing>" "<property name='expand'>False</property>" "<property name='homogeneous'>True</property>" "</packing>" "</child>" "<child>" "<object class='GtkSeparatorToolItem' id='separator1'>" "<property name='visible'>True</property>" "<property name='can_focus'>False</property>" "</object>" "<packing>" "<property name='expand'>False</property>" "<property name='homogeneous'>False</property>" "</packing>" "</child>" "<child>" "<object class='GtkToolButton' id='toolbutton_quit'>" "<property name='visible'>True</property>" "<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>" "</object>" "<packing>" "<property name='expand'>False</property>" "<property name='homogeneous'>True</property>" "</packing>" "</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 = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); if (!gmenu) g_warning("GMenu not found"); else { auto pMenuBar = Gtk::make_managed<Gtk::MenuBar>(gmenu); // Add the MenuBar to the window: m_VBox.pack_start(*pMenuBar, Gtk::PACK_SHRINK); } // Get the toolbar and add it to a container widget: Gtk::Toolbar* toolbar = nullptr; m_refBuilder->get_widget("toolbar", toolbar); if (!toolbar) g_warning("GtkToolbar not found"); else m_VBox.pack_start(*toolbar, Gtk::PACK_SHRINK); } 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::PrintOperationResult result) { //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 = 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::PrintOperationAction 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::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: 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) { auto 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()); }