| 224 | | #include <iomanip> |
| 225 | | |
| 226 | | #include <ctime> |
| 227 | | #include <cstring> |
| 228 | | #include <cmath> |
| 229 | | |
| 230 | | #include <gtkmm/textbuffer.h> |
| 231 | | #include <libxml++/libxml++.h> |
| 232 | | |
| 233 | | #include <libinftextgtk/inf-text-gtk-buffer.h> |
| 234 | | #include "util/i18n.hpp" |
| 235 | | |
| 236 | | struct TagComparator |
| 237 | | { |
| 238 | | bool operator()(GtkTextTag* first, GtkTextTag* second) const |
| 239 | | { |
| 240 | | return gtk_text_tag_get_priority(first) < |
| 241 | | gtk_text_tag_get_priority(second); |
| 242 | | } |
| 243 | | }; |
| 244 | | |
| 245 | | // Sort tags by priority, so that we declare them in order |
| 246 | | typedef std::set<GtkTextTag*, TagComparator> priority_tag_set; |
| 247 | | |
| 248 | | // We don't use Glib::ustring::compose for now because |
| 249 | | // it's formatting support does not compile properly under |
| 250 | | // Windows. See https://bugzilla.gnome.org/show_bug.cgi?id=599340 |
| 251 | | Glib::ustring uprintf(gchar const* fmt, ...) |
| 252 | | { |
| 253 | | va_list args; |
| 254 | | va_start(args, fmt); |
| 255 | | gchar* str = g_strdup_vprintf(fmt, args); |
| 256 | | va_end(args); |
| 257 | | Glib::ustring result; |
| 258 | | try |
| 259 | | { |
| 260 | | result = str; |
| 261 | | } |
| 262 | | catch (...) |
| 263 | | { |
| 264 | | g_free(str); |
| 265 | | throw; |
| 266 | | } |
| 267 | | g_free(str); |
| 268 | | return result; |
| 269 | | } |
| 270 | | |
| 271 | | unsigned int color_to_rgb(GdkColor* color) |
| 272 | | { |
| 273 | | return ((color->red & 0xff00) << 8) |
| 274 | | | (color->green & 0xff00) |
| 275 | | | ((color->blue & 0xff00) >> 8); |
| 276 | | } |
| 277 | | |
| 278 | | // write the Gtk::TextBuffer from document into content, inserting <span/>s for |
| 279 | | // line breaks and authorship of chunks of text, also save all users |
| 280 | | // encountered and the total number of lines dumped |
| 281 | | void dump_buffer(Gobby::DocWindow& document, |
| 282 | | xmlpp::Element* content, |
| 283 | | std::set<InfTextUser*>& users, |
| 284 | | priority_tag_set& tags, |
| 285 | | unsigned int& line_counter) |
| 286 | | { |
| 287 | | using namespace Gobby; |
| 288 | | users.clear(); |
| 289 | | tags.clear(); |
| 290 | | line_counter = 1; |
| 291 | | xmlpp::Element* last_node = content; |
| 292 | | xmlpp::Element* line_no = last_node->add_child("span"); |
| 293 | | line_no->set_attribute("class", "line_no"); |
| 294 | | line_no->set_attribute("id", "line_1"); |
| 295 | | |
| 296 | | GtkTextBuffer* buffer = GTK_TEXT_BUFFER(document.get_text_buffer()); |
| 297 | | InfTextGtkBuffer* inf_buffer |
| 298 | | = INF_TEXT_GTK_BUFFER( |
| 299 | | inf_session_get_buffer(INF_SESSION(document.get_session()))); |
| 300 | | |
| 301 | | GtkTextIter begin; |
| 302 | | gtk_text_buffer_get_start_iter(buffer, &begin); |
| 303 | | { |
| 304 | | GtkTextIter end; |
| 305 | | gtk_text_buffer_get_end_iter(buffer, &end); |
| 306 | | gtk_source_buffer_ensure_highlight( |
| 307 | | GTK_SOURCE_BUFFER(buffer), |
| 308 | | &begin, |
| 309 | | &end); |
| 310 | | } |
| 311 | | |
| 312 | | while(!gtk_text_iter_is_end(&begin)) |
| 313 | | { |
| 314 | | GSList* current_tags = gtk_text_iter_get_tags(&begin); |
| 315 | | Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> > handle( |
| 316 | | current_tags, Glib::OWNERSHIP_SHALLOW); |
| 317 | | Glib::ustring classes; |
| 318 | | for(GSList* tag = current_tags; tag != 0; tag = tag->next) |
| 319 | | { |
| 320 | | if(!classes.empty()) |
| 321 | | classes += ' '; |
| 322 | | gchar* tag_name = g_strdup_printf( |
| 323 | | "tag_%p", static_cast<void*>(tag->data)); |
| 324 | | classes += tag_name; |
| 325 | | g_free(tag_name); |
| 326 | | tags.insert(GTK_TEXT_TAG(tag->data)); |
| 327 | | } |
| 328 | | |
| 329 | | last_node = last_node->add_child("span"); |
| 330 | | if (!classes.empty()) |
| 331 | | last_node->set_attribute("class", classes); |
| 332 | | |
| 333 | | InfTextUser* new_user |
| 334 | | = inf_text_gtk_buffer_get_author(inf_buffer, &begin); |
| 335 | | if (new_user) { |
| 336 | | last_node->set_attribute( |
| 337 | | "title", |
| 338 | | uprintf(_("written by: %s"), |
| 339 | | inf_user_get_name(INF_USER(new_user)))); |
| 340 | | users.insert(new_user); |
| 341 | | } |
| 342 | | |
| 343 | | GtkTextIter next = begin; |
| 344 | | gtk_text_iter_forward_to_tag_toggle(&next, 0); |
| 345 | | |
| 346 | | // split text by newlines so we can insert line number elements |
| 347 | | gchar* text = gtk_text_iter_get_text(&begin, &next); |
| 348 | | try |
| 349 | | { |
| 350 | | gchar const* last_pos = text; |
| 351 | | for (gchar const* i = last_pos; *i; ++i) { |
| 352 | | if (*i != '\n') |
| 353 | | continue; |
| 354 | | |
| 355 | | ++line_counter; |
| 356 | | |
| 357 | | gchar const* next_pos = i; |
| 358 | | ++next_pos; |
| 359 | | last_node->add_child_text(Glib::ustring(last_pos, next_pos)); |
| 360 | | last_pos = next_pos; |
| 361 | | |
| 362 | | // drop author <span/> for a moment for the line number <span/> |
| 363 | | line_no = last_node->add_child("span"); |
| 364 | | line_no->set_attribute("class", "line_no"); |
| 365 | | line_no->set_attribute( |
| 366 | | "id", |
| 367 | | uprintf("line_%d", line_counter)); |
| 368 | | } |
| 369 | | |
| 370 | | last_node->add_child_text(Glib::ustring(last_pos)); |
| 371 | | } |
| 372 | | catch(...) |
| 373 | | { |
| 374 | | g_free(text); |
| 375 | | throw; |
| 376 | | } |
| 377 | | g_free(text); |
| 378 | | last_node = last_node->get_parent(); |
| 379 | | |
| 380 | | begin = next; |
| 381 | | } |
| 382 | | } |
| 383 | | |
| 384 | | // some random interesting information/advertisement to be put at the end of |
| 385 | | // the html output |
| 386 | | void dump_info(xmlpp::Element* node, Gobby::DocWindow& document) { |
| 387 | | using namespace Gobby; |
| 388 | | // put current time |
| 389 | | char const* time_str; |
| 390 | | int const n = 128; |
| 391 | | char buf[n]; |
| 392 | | { |
| 393 | | std::time_t now; |
| 394 | | std::time(&now); |
| 395 | | // TODO: localtime is not threadsafe, use boost.date_time!! |
| 396 | | if (std::strftime(buf, n, "%c", localtime(&now))) |
| 397 | | time_str = buf; |
| 398 | | else |
| 399 | | time_str = _("<unable to print date>"); |
| 400 | | } |
| 401 | | |
| 402 | | // put document metadata, like path, hostname of infinoted |
| 403 | | // TODO: figure out what interesting info we can pull out of the session |
| 404 | | char session_info[] = "<placeholder for session info and path>"; |
| 405 | | |
| 406 | | // %1$s is information about the document's location, session name, |
| 407 | | // %2$s is current date as formatted by %c, |
| 408 | | // %3$s is a link to the gobby site |
| 409 | | char const* translated = |
| 410 | | _("Document generated from %1$s at %2$s by %3$s"); |
| 411 | | char const* p = std::strstr(translated, "%3$s"); |
| 412 | | g_assert(p); |
| 413 | | node->add_child_text( |
| 414 | | uprintf(Glib::ustring(translated, p).c_str(), session_info, time_str)); |
| 415 | | |
| 416 | | xmlpp::Element* link = node->add_child("a"); |
| 417 | | link->set_attribute("href", "http://gobby.0x539.de/"); |
| 418 | | link->add_child_text(PACKAGE_STRING); |
| 419 | | |
| 420 | | if (*p != '\0') |
| 421 | | node->add_child_text( |
| 422 | | uprintf(p+4 , session_info, time_str)); |
| 423 | | } |
| 424 | | |
| 425 | | // list each author before the actual text |
| 426 | | void dump_user_list(xmlpp::Element* list, |
| 427 | | const std::set<InfTextUser*>& users) { |
| 428 | | for(std::set<InfTextUser*>::const_iterator i = users.begin(); |
| 429 | | i != users.end(); |
| 430 | | ++i) |
| 431 | | { |
| 432 | | gdouble hue = inf_text_user_get_hue(*i); |
| 433 | | hue = std::fmod(hue, 1); |
| 434 | | |
| 435 | | Gdk::Color c; |
| 436 | | c.set_hsv(360.0 * hue, 0.35, 1.0); |
| 437 | | gchar const* name = inf_user_get_name(INF_USER(*i)); |
| 438 | | const unsigned int rgb = color_to_rgb(c.gobj()); |
| 439 | | |
| 440 | | xmlpp::Element* item = list->add_child("li"); |
| 441 | | item->add_child_text(name); |
| 442 | | item->set_attribute( |
| 443 | | "style", |
| 444 | | uprintf("background-color: #%06x;\n", rgb)); |
| 445 | | } |
| 446 | | } |
| 447 | | |
| 448 | | void dump_tags_style(xmlpp::Element* css, |
| 449 | | const priority_tag_set& tags) |
| 450 | | { |
| 451 | | for(priority_tag_set::const_iterator i = tags.begin(); i != tags.end(); ++i) |
| 452 | | { |
| 453 | | GdkColor* fg, * bg; |
| 454 | | gint weight; |
| 455 | | gboolean underline; |
| 456 | | PangoStyle style; |
| 457 | | gboolean fg_set, bg_set, weight_set, underline_set, style_set; |
| 458 | | g_object_get(G_OBJECT(*i), |
| 459 | | "background-gdk", &bg, |
| 460 | | "foreground-gdk", &fg, |
| 461 | | "weight", &weight, |
| 462 | | "underline", &underline, |
| 463 | | "style", &style, |
| 464 | | "background-set", &bg_set, |
| 465 | | "foreground-set", &fg_set, |
| 466 | | "weight-set", &weight_set, |
| 467 | | "underline-set", &underline_set, |
| 468 | | "style-set", &style_set, |
| 469 | | NULL); |
| 470 | | const unsigned int bg_rgb = color_to_rgb(bg); |
| 471 | | const unsigned int fg_rgb = color_to_rgb(fg); |
| 472 | | gdk_color_free(fg); |
| 473 | | gdk_color_free(bg); |
| 474 | | css->add_child_text( |
| 475 | | uprintf(".tag_%p {\n", static_cast<void*>(*i))); |
| 476 | | if(fg_set) |
| 477 | | css->add_child_text(uprintf( |
| 478 | | " color: #%06x;\n", |
| 479 | | fg_rgb)); |
| 480 | | if(bg_set) |
| 481 | | css->add_child_text(uprintf( |
| 482 | | " background-color: #%06x;\n", |
| 483 | | bg_rgb)); |
| 484 | | if(weight_set) |
| 485 | | css->add_child_text(uprintf( |
| 486 | | " font-weight: %d;\n", |
| 487 | | weight)); |
| 488 | | if(underline_set) |
| 489 | | css->add_child_text(uprintf( |
| 490 | | " text-decoration: %s;\n", |
| 491 | | underline ? "underline" : "none")); |
| 492 | | css->add_child_text("}\n"); |
| 493 | | } |
| 494 | | } |
| 495 | | |
| 496 | | // generate xhtml representation of the document and write it to the |
| 497 | | // specified location in the filesystem |
| 498 | | void export_html(Gobby::DocWindow& document, const Glib::ustring& output_path) { |
| 499 | | using namespace Gobby; |
| 500 | | xmlpp::Document output; |
| 501 | | |
| 502 | | output.set_internal_subset("html", |
| 503 | | "-//W3C//DTD XHTML 1.1//EN", |
| 504 | | "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"); |
| 505 | | |
| 506 | | xmlpp::Element |
| 507 | | * root = output.create_root_node("html", "http://www.w3.org/1999/xhtml"), |
| 508 | | * head = root->add_child("head"), |
| 509 | | * body = root->add_child("body"), |
| 510 | | * title = head->add_child("title"), |
| 511 | | * style = head->add_child("style"), |
| 512 | | * h1 = body->add_child("h1"), |
| 513 | | * h2 = body->add_child("h2"), |
| 514 | | * user_list = body->add_child("ul"), |
| 515 | | * content = body->add_child("pre"), |
| 516 | | * info = body->add_child("p"); |
| 517 | | |
| 518 | | const Glib::ustring& document_name = document.get_title(); |
| 519 | | title->add_child_text(document_name + " - infinote document"); |
| 520 | | |
| 521 | | h1->add_child_text(document_name); |
| 522 | | |
| 523 | | content->set_attribute("class", "document"); |
| 524 | | |
| 525 | | std::set<InfTextUser*> users; |
| 526 | | priority_tag_set tags; |
| 527 | | unsigned int line_counter; |
| 528 | | dump_buffer(document, content, users, tags, line_counter); |
| 529 | | |
| 530 | | h2->add_child_text(_("Participants")); |
| 531 | | |
| 532 | | info->set_attribute("class", "info"); |
| 533 | | dump_info(info, document); |
| 534 | | |
| 535 | | style->set_attribute("type", "text/css"); |
| 536 | | dump_user_list(user_list, users); |
| 537 | | dump_tags_style(style, tags); |
| 538 | | if (!user_list->cobj()->children) { |
| 539 | | body->remove_child(h2); |
| 540 | | body->remove_child(user_list); |
| 541 | | } |
| 542 | | |
| 543 | | style->add_child_text( |
| 544 | | "h1 {\n" |
| 545 | | " font-family:\n" |
| 546 | | "}\n" |
| 547 | | ".document {\n" |
| 548 | | " border-top: 1px solid gray;\n" |
| 549 | | " border-bottom: 1px solid black;\n" |
| 550 | | " padding-bottom: 1.2em;\n" |
| 551 | | " counter-reset: line;\n" |
| 552 | | "}\n" |
| 553 | | ".line_no:before {\n" |
| 554 | | " content: counter(line);\n" |
| 555 | | " counter-increment: line;\n" |
| 556 | | "}\n" |
| 557 | | ".info {\n" |
| 558 | | " font-size: small;\n" |
| 559 | | "}\n"); |
| 560 | | |
| 561 | | style->add_child_text( |
| 562 | | uprintf( |
| 563 | | ".line_no {\n" |
| 564 | | " position: absolute;\n" |
| 565 | | " float: left;\n" |
| 566 | | " clear: left;\n" |
| 567 | | " margin-left: -%1$uem;\n" |
| 568 | | " color: gray;\n" |
| 569 | | "}\n" |
| 570 | | ".document {\n" |
| 571 | | " padding-left: %1$uem\n" |
| 572 | | "}\n", |
| 573 | | static_cast<unsigned int>(std::log(line_counter)/std::log(10))+1)); |
| 574 | | |
| 575 | | output.write_to_file(output_path, "utf-8"); |
| 576 | | } |
| 577 | | |