1 /* 2 * gnote 3 * 4 * Copyright (C) 2010-2013,2015-2017,2019-2021 Aurimas Cernius 5 * Copyright (C) 2009 Hubert Figuiere 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 22 23 #include <glibmm/i18n.h> 24 #include <glibmm/miscutils.h> 25 #include <gtkmm/image.h> 26 #include <gtkmm/printoperation.h> 27 28 #include "debug.hpp" 29 #include "iactionmanager.hpp" 30 #include "notetag.hpp" 31 #include "notewindow.hpp" 32 #include "printnotesnoteaddin.hpp" 33 #include "utils.hpp" 34 35 namespace printnotes { 36 PrintNotesModule()37 PrintNotesModule::PrintNotesModule() 38 { 39 ADD_INTERFACE_IMPL(PrintNotesNoteAddin); 40 } 41 initialize()42 void PrintNotesNoteAddin::initialize() 43 { 44 } 45 46 shutdown()47 void PrintNotesNoteAddin::shutdown() 48 { 49 } 50 51 on_note_opened()52 void PrintNotesNoteAddin::on_note_opened() 53 { 54 register_main_window_action_callback("printnotes-print", 55 sigc::mem_fun(*this, &PrintNotesNoteAddin::print_button_clicked)); 56 } 57 58 get_actions_popover_widgets() const59 std::vector<gnote::PopoverWidget> PrintNotesNoteAddin::get_actions_popover_widgets() const 60 { 61 auto widgets = NoteAddin::get_actions_popover_widgets(); 62 auto button = gnote::utils::create_popover_button("win.printnotes-print", _("Print…")); 63 widgets.push_back(gnote::PopoverWidget::create_for_note(gnote::PRINT_ORDER, button)); 64 return widgets; 65 } 66 67 print_button_clicked(const Glib::VariantBase &)68 void PrintNotesNoteAddin::print_button_clicked(const Glib::VariantBase&) 69 { 70 try { 71 m_print_op = Gtk::PrintOperation::create(); 72 m_print_op->set_job_name(get_note()->get_title()); 73 74 Glib::RefPtr<Gtk::PrintSettings> settings = Gtk::PrintSettings::create(); 75 76 Glib::ustring dir = Glib::get_user_special_dir(Glib::USER_DIRECTORY_DOCUMENTS); 77 if (dir.empty()) { 78 dir = Glib::get_home_dir(); 79 } 80 Glib::ustring ext; 81 if (settings->get(Gtk::PrintSettings::Keys::OUTPUT_FILE_FORMAT) == "ps") { 82 ext = ".ps"; 83 } 84 else { 85 ext = ".pdf"; 86 } 87 88 Glib::ustring uri = "file://"; 89 uri += dir + "/gnotes" + ext; 90 settings->set (Gtk::PrintSettings::Keys::OUTPUT_URI, uri); 91 m_print_op->set_print_settings (settings); 92 93 m_print_op->signal_begin_print().connect( 94 sigc::mem_fun(*this, &PrintNotesNoteAddin::on_begin_print)); 95 m_print_op->signal_draw_page().connect( 96 sigc::mem_fun(*this, &PrintNotesNoteAddin::on_draw_page)); 97 m_print_op->signal_end_print().connect( 98 sigc::mem_fun(*this, &PrintNotesNoteAddin::on_end_print)); 99 100 m_print_op->run(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG, *get_host_window()); 101 } 102 catch (const sharp::Exception & e) 103 { 104 DBG_OUT("Exception while printing %s: %s", get_note()->get_title().c_str(), 105 e.what()); 106 gnote::utils::HIGMessageDialog dlg(get_host_window(), 107 GTK_DIALOG_MODAL, 108 Gtk::MESSAGE_ERROR, 109 Gtk::BUTTONS_OK, 110 _("Error printing note"), 111 e.what()); 112 dlg.run (); 113 } 114 m_print_op.reset(); 115 } 116 117 get_paragraph_attributes(const Glib::RefPtr<Pango::Layout> & layout,double dpiX,int & indentation,Gtk::TextIter & position,const Gtk::TextIter & limit)118 std::vector<Pango::Attribute> PrintNotesNoteAddin::get_paragraph_attributes(const Glib::RefPtr<Pango::Layout> & layout, 119 double dpiX, 120 int & indentation, 121 Gtk::TextIter & position, 122 const Gtk::TextIter & limit) 123 { 124 std::vector<Pango::Attribute> attributes; 125 indentation = 0; 126 127 Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> > tags = position.get_tags(); 128 position.forward_to_tag_toggle(Glib::RefPtr<Gtk::TextTag>(NULL)); 129 if (position.compare (limit) > 0) { 130 position = limit; 131 } 132 133 Glib::RefPtr<Gdk::Screen> screen = get_window()->get_screen(); 134 double screen_dpiX = screen->get_width_mm() * 254 / screen->get_width(); 135 136 for(Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> >::const_iterator iter = tags.begin(); 137 iter != tags.end(); ++iter) { 138 139 Glib::RefPtr<Gtk::TextTag> tag(*iter); 140 141 if (tag->property_paragraph_background_set()) { 142 Gdk::RGBA color = tag->property_paragraph_background_rgba(); 143 attributes.push_back(Pango::Attribute::create_attr_background( 144 color.get_red_u(), color.get_green_u(), 145 color.get_blue_u())); 146 } 147 if (tag->property_foreground_set()) { 148 Gdk::RGBA color = tag->property_foreground_rgba(); 149 attributes.push_back(Pango::Attribute::create_attr_foreground( 150 color.get_red_u(), color.get_green_u(), 151 color.get_blue_u())); 152 } 153 if (tag->property_indent_set()) { 154 layout->set_indent(tag->property_indent()); 155 } 156 if (tag->property_left_margin_set()) { 157 indentation = (int)(tag->property_left_margin() / screen_dpiX * dpiX); 158 } 159 if (tag->property_right_margin_set()) { 160 indentation = (int)(tag->property_right_margin() / screen_dpiX * dpiX); 161 } 162 // if (tag->property_font_desc()) { 163 attributes.push_back( 164 Pango::Attribute::create_attr_font_desc (tag->property_font_desc())); 165 // } 166 if (tag->property_family_set()) { 167 attributes.push_back( 168 Pango::Attribute::create_attr_family (tag->property_family())); 169 } 170 if (tag->property_size_set()) { 171 attributes.push_back(Pango::Attribute::create_attr_size ( 172 tag->property_size())); 173 } 174 if (tag->property_style_set()) { 175 attributes.push_back(Pango::Attribute::create_attr_style ( 176 tag->property_style())); 177 } 178 if (tag->property_underline_set() 179 && tag->property_underline() != Pango::UNDERLINE_ERROR) { 180 attributes.push_back( 181 Pango::Attribute::create_attr_underline ( 182 tag->property_underline())); 183 } 184 if (tag->property_weight_set()) { 185 attributes.push_back( 186 Pango::Attribute::create_attr_weight( 187 Pango::Weight(tag->property_weight().get_value()))); 188 } 189 if (tag->property_strikethrough_set()) { 190 attributes.push_back( 191 Pango::Attribute::create_attr_strikethrough ( 192 tag->property_strikethrough())); 193 } 194 if (tag->property_rise_set()) { 195 attributes.push_back(Pango::Attribute::create_attr_rise ( 196 tag->property_rise())); 197 } 198 if (tag->property_scale_set()) { 199 attributes.push_back(Pango::Attribute::create_attr_scale ( 200 tag->property_scale())); 201 } 202 if (tag->property_stretch_set()) { 203 attributes.push_back(Pango::Attribute::create_attr_stretch ( 204 tag->property_stretch())); 205 } 206 } 207 208 return attributes; 209 } 210 211 Glib::RefPtr<Pango::Layout> create_layout_for_paragraph(const Glib::RefPtr<Gtk::PrintContext> & context,Gtk::TextIter p_start,Gtk::TextIter p_end,int & indentation)212 PrintNotesNoteAddin::create_layout_for_paragraph(const Glib::RefPtr<Gtk::PrintContext> & context, 213 Gtk::TextIter p_start, 214 Gtk::TextIter p_end, 215 int & indentation) 216 { 217 Glib::RefPtr<Pango::Layout> layout = context->create_pango_layout(); 218 219 layout->set_font_description( 220 get_window()->editor()->get_pango_context()->get_font_description()); 221 int start_index = p_start.get_line_index(); 222 indentation = 0; 223 224 double dpiX = context->get_dpi_x(); 225 { 226 Pango::AttrList attr_list; 227 228 Gtk::TextIter segm_start = p_start; 229 Gtk::TextIter segm_end; 230 231 while (segm_start.compare (p_end) < 0) { 232 segm_end = segm_start; 233 auto attrs = get_paragraph_attributes(layout, dpiX, indentation, segm_end, p_end); 234 235 guint si = (guint) (segm_start.get_line_index() - start_index); 236 guint ei = (guint) (segm_end.get_line_index() - start_index); 237 238 for(auto & a : attrs) { 239 a.set_start_index(si); 240 a.set_end_index(ei); 241 attr_list.insert(a); 242 } 243 segm_start = segm_end; 244 } 245 246 layout->set_attributes(attr_list); 247 } 248 249 gnote::DepthNoteTag::Ptr depth = get_buffer()->find_depth_tag(p_start); 250 if(depth) { 251 indentation += ((int) (dpiX / 3)) * depth->get_depth(); 252 } 253 layout->set_width(pango_units_from_double((int)context->get_width() - 254 m_margin_left - m_margin_right - indentation)); 255 layout->set_wrap (Pango::WRAP_WORD_CHAR); 256 layout->set_text (get_buffer()->get_slice (p_start, p_end, false)); 257 return layout; 258 } 259 260 261 Glib::RefPtr<Pango::Layout> create_layout_for_pagenumbers(const Glib::RefPtr<Gtk::PrintContext> & context,int page_number,int total_pages)262 PrintNotesNoteAddin::create_layout_for_pagenumbers(const Glib::RefPtr<Gtk::PrintContext> & context, 263 int page_number, int total_pages) 264 { 265 Glib::RefPtr<Pango::Layout> layout = context->create_pango_layout(); 266 Pango::FontDescription font_desc = get_window()->editor()->get_pango_context()->get_font_description(); 267 font_desc.set_style(Pango::STYLE_NORMAL); 268 font_desc.set_weight(Pango::WEIGHT_LIGHT); 269 layout->set_font_description(font_desc); 270 layout->set_width(pango_units_from_double((int)context->get_width())); 271 272 // %1 is the page number, %2 is the total number of pages 273 Glib::ustring footer_left = Glib::ustring::compose(_("Page %1 of %2"), 274 page_number, total_pages); 275 layout->set_alignment(Pango::ALIGN_LEFT); 276 layout->set_text (footer_left); 277 278 return layout; 279 } 280 281 282 Glib::RefPtr<Pango::Layout> create_layout_for_timestamp(const Glib::RefPtr<Gtk::PrintContext> & context)283 PrintNotesNoteAddin::create_layout_for_timestamp(const Glib::RefPtr<Gtk::PrintContext> & context) 284 { 285 Glib::ustring timestamp = sharp::date_time_to_string(Glib::DateTime::create_now_local(), "%c"); 286 287 Glib::RefPtr<Pango::Layout> layout = context->create_pango_layout (); 288 Pango::FontDescription font_desc = get_window()->editor()->get_pango_context()->get_font_description(); 289 font_desc.set_style(Pango::STYLE_NORMAL); 290 font_desc.set_weight(Pango::WEIGHT_LIGHT); 291 layout->set_font_description(font_desc); 292 layout->set_width(pango_units_from_double((int) context->get_width())); 293 294 layout->set_alignment(Pango::ALIGN_RIGHT); 295 layout->set_text (timestamp); 296 297 return layout; 298 } 299 compute_footer_height(const Glib::RefPtr<Gtk::PrintContext> & context)300 int PrintNotesNoteAddin::compute_footer_height(const Glib::RefPtr<Gtk::PrintContext> & context) 301 { 302 Glib::RefPtr<Pango::Layout> layout = create_layout_for_timestamp(context); 303 Pango::Rectangle ink_rect; 304 Pango::Rectangle logical_rect; 305 layout->get_extents(ink_rect, logical_rect); 306 return pango_units_to_double(ink_rect.get_height()) 307 + cm_to_pixel(0.5, context->get_dpi_y()); 308 } 309 310 on_begin_print(const Glib::RefPtr<Gtk::PrintContext> & context)311 void PrintNotesNoteAddin::on_begin_print(const Glib::RefPtr<Gtk::PrintContext>& context) 312 { 313 m_timestamp_footer = create_layout_for_timestamp(context); 314 // Create and initialize the page margins 315 m_margin_top = cm_to_pixel (1.5, context->get_dpi_y()); 316 m_margin_left = cm_to_pixel (1, context->get_dpi_x()); 317 m_margin_right = cm_to_pixel (1, context->get_dpi_x()); 318 m_margin_bottom = 0; 319 double max_height = pango_units_from_double(context->get_height() 320 - m_margin_top - m_margin_bottom 321 - compute_footer_height(context)); 322 323 DBG_OUT("margins = %d %d %d %d", m_margin_top, m_margin_left, 324 m_margin_right, m_margin_bottom); 325 326 m_page_breaks.clear(); 327 328 Gtk::TextIter position; 329 Gtk::TextIter end_iter; 330 get_buffer()->get_bounds (position, end_iter); 331 332 double page_height = 0; 333 bool done = position.compare (end_iter) >= 0; 334 while (!done) { 335 Gtk::TextIter line_end = position; 336 if (!line_end.ends_line ()) { 337 line_end.forward_to_line_end (); 338 } 339 340 int paragraph_number = position.get_line(); 341 int indentation = 0; 342 Glib::RefPtr<Pango::Layout> layout = create_layout_for_paragraph( 343 context, position, line_end, indentation); 344 345 Pango::Rectangle ink_rect; 346 Pango::Rectangle logical_rect; 347 for(int line_in_paragraph = 0; line_in_paragraph < layout->get_line_count(); 348 line_in_paragraph++) { 349 Glib::RefPtr<Pango::LayoutLine> line = layout->get_line(line_in_paragraph); 350 line->get_extents (ink_rect, logical_rect); 351 352 if ((page_height + logical_rect.get_height()) >= max_height) { 353 PageBreak(paragraph_number, line_in_paragraph); 354 m_page_breaks.push_back (PageBreak(paragraph_number, line_in_paragraph)); 355 page_height = 0; 356 } 357 358 page_height += logical_rect.get_height(); 359 360 } 361 position.forward_line (); 362 done = position.compare (end_iter) >= 0; 363 } 364 365 m_print_op->set_n_pages(m_page_breaks.size() + 1); 366 } 367 368 369 on_draw_page(const Glib::RefPtr<Gtk::PrintContext> & context,guint page_nr)370 void PrintNotesNoteAddin::on_draw_page(const Glib::RefPtr<Gtk::PrintContext>& context, guint page_nr) 371 { 372 Cairo::RefPtr<Cairo::Context> cr = context->get_cairo_context(); 373 cr->move_to (m_margin_left, m_margin_top); 374 375 PageBreak start; 376 if (page_nr != 0) { 377 start = m_page_breaks [page_nr - 1]; 378 } 379 380 PageBreak end(-1, -1); 381 if (m_page_breaks.size() > page_nr) { 382 end = m_page_breaks [page_nr]; 383 } 384 385 Gtk::TextIter position; 386 Gtk::TextIter end_iter; 387 get_buffer()->get_bounds (position, end_iter); 388 389 // Fast-forward to the starting line 390 while (position.get_line() < start.get_paragraph()) { 391 position.forward_line (); 392 } 393 394 bool done = position.compare (end_iter) >= 0; 395 while (!done) { 396 Gtk::TextIter line_end = position; 397 if (!line_end.ends_line ()) { 398 line_end.forward_to_line_end (); 399 } 400 401 402 int paragraph_number = position.get_line(); 403 int indentation; 404 405 { 406 Glib::RefPtr<Pango::Layout> layout = 407 create_layout_for_paragraph (context,position, line_end, indentation); 408 409 for(int line_number = 0; 410 line_number < layout->get_line_count() && !done; 411 line_number++) { 412 // Skip the lines up to the starting line in the 413 // first paragraph on this page 414 if ((paragraph_number == start.get_paragraph()) && 415 (line_number < start.get_line())) { 416 continue; 417 } 418 // Break as soon as we hit the end line 419 if ((paragraph_number == end.get_paragraph()) && 420 (line_number == end.get_line())) { 421 done = true; 422 break; 423 } 424 425 426 427 Glib::RefPtr<Pango::LayoutLine> line = layout->get_line(line_number); 428 429 Pango::Rectangle ink_rect; 430 Pango::Rectangle logical_rect; 431 line->get_extents (ink_rect, logical_rect); 432 433 double curX, curY; 434 cr->get_current_point(curX, curY); 435 cr->move_to (m_margin_left + indentation, curY); 436 int line_height = pango_units_to_double(logical_rect.get_height()); 437 438 double x, y; 439 x = m_margin_left + indentation; 440 cr->get_current_point(curX, curY); 441 y = curY + line_height; 442 pango_cairo_show_layout_line(cr->cobj(), line->gobj()); 443 cr->move_to(x, y); 444 } 445 } 446 447 position.forward_line (); 448 done = done || (position.compare (end_iter) >= 0); 449 } 450 451 // Print the footer 452 int total_height = context->get_height(); 453 int total_width = context->get_width(); 454 int footer_height = 0; 455 456 double footer_anchor_x, footer_anchor_y; 457 458 { 459 Glib::RefPtr<Pango::Layout> pages_footer 460 = create_layout_for_pagenumbers (context, page_nr + 1, 461 m_page_breaks.size() + 1); 462 463 Pango::Rectangle ink_footer_rect; 464 Pango::Rectangle logical_footer_rect; 465 pages_footer->get_extents(ink_footer_rect, logical_footer_rect); 466 467 footer_anchor_x = cm_to_pixel(0.5, context->get_dpi_x()); 468 footer_anchor_y = total_height - m_margin_bottom; 469 footer_height = pango_units_to_double(logical_footer_rect.get_height()); 470 471 cr->move_to(total_width - pango_units_to_double(logical_footer_rect.get_width()) - cm_to_pixel(0.5, context->get_dpi_x()), footer_anchor_y); 472 473 pango_cairo_show_layout_line(cr->cobj(), 474 (pages_footer->get_line(0))->gobj()); 475 476 } 477 478 cr->move_to(footer_anchor_x, footer_anchor_y); 479 pango_cairo_show_layout_line(cr->cobj(), 480 (m_timestamp_footer->get_line(0))->gobj()); 481 482 cr->move_to(cm_to_pixel(0.5, context->get_dpi_x()), 483 total_height - m_margin_bottom - footer_height); 484 cr->line_to(total_width - cm_to_pixel(0.5, context->get_dpi_x()), 485 total_height - m_margin_bottom - footer_height); 486 cr->stroke(); 487 } 488 489 on_end_print(const Glib::RefPtr<Gtk::PrintContext> &)490 void PrintNotesNoteAddin::on_end_print(const Glib::RefPtr<Gtk::PrintContext>&) 491 { 492 m_page_breaks.clear (); 493 m_timestamp_footer.reset(); 494 } 495 496 } 497