/***********************************************************************************

    Copyright (C) 2007-2018 Ahmet Öztürk (aoz_2@yahoo.com)

    This file is part of Lifeograph.

    Lifeograph is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Lifeograph is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Lifeograph.  If not, see <http://www.gnu.org/licenses/>.

***********************************************************************************/


#include "lifeograph.hpp"
#include "app_window.hpp"
#include "printing.hpp"
#include "diary.hpp"


using namespace LIFEO;


void
PrintableText::render( const Glib::RefPtr< Gtk::PrintContext > &print_context )
{
    // Get a Cairo Context, which is used as a drawing board:
    Cairo::RefPtr< Cairo::Context > cr( print_context->get_cairo_context() );

    // We'll use black letters:
    cr->set_source_rgb( 0, 0, 0 );

    // Render Pango LayoutLines over the Cairo context:
#if( ( PANGOMM_MAJOR_VERSION > 2 ) || \
     ( ( PANGOMM_MAJOR_VERSION == 2 ) && ( PANGOMM_MINOR_VERSION >= 28 ) ) )
    Pango::LayoutIter iter( m_layout->get_iter() );
#else
    Pango::LayoutIter iter;
    m_layout->get_iter( iter );
#endif

    double start_pos( 0 );
    bool flag_1st( true );
    int line_index( 0 );
    do
    {
        if( line_index > m_line_end )
            break;
        else if( line_index >= m_line_begin )
        {
            Glib::RefPtr< Pango::LayoutLine > layout_line = iter.get_line();
            Pango::Rectangle logical_rect = iter.get_line_logical_extents();
            int baseline = iter.get_baseline();

            if( flag_1st )
            {
                start_pos = logical_rect.get_y() / 1024.0 - m_v_offset;
                flag_1st = false;
            }

            cr->move_to( logical_rect.get_x() / 1024.0, baseline / 1024.0 - start_pos );

            layout_line->show_in_cairo_context( cr );
        }

        line_index++;
    }
    while( iter.next_line() );
}

void
PrintableImage::render( const Glib::RefPtr< Gtk::PrintContext > &print_context )
{
    Cairo::RefPtr<Cairo::Context> cr = print_context->get_cairo_context();

    int width( m_pixbuf->get_width() );
    int height( m_pixbuf->get_height() );

    Cairo::RefPtr< Cairo::ImageSurface > m_image_surface = Cairo::ImageSurface::create(
            m_pixbuf->get_has_alpha() ? Cairo::FORMAT_ARGB32 : Cairo::FORMAT_RGB24,
            width, height );
    Gdk::Cairo::set_source_pixbuf(
            cr, m_pixbuf, ( print_context->get_width() - width ) / 2.0, m_v_offset );
    cr->paint();
}

// PRINT OPERATION =================================================================================
PrintOpr::PrintOpr()
{
    m_refSettings = Gtk::PrintSettings::create();
    m_refPageSetup = Gtk::PageSetup::create();
    set_print_settings( m_refSettings );
    set_default_page_setup( m_refPageSetup );
    set_embed_page_setup( true );
    set_track_print_status();
}

PrintOpr::~PrintOpr()
{
}

void
PrintOpr::clear_content()
{
    for( Printables::iterator iter = m_content.begin(); iter != m_content.end(); ++iter )
        delete *iter;

    m_content.clear();
}

void
PrintOpr::init_variables( const Glib::RefPtr< Gtk::PrintContext >& print_context  )
{
    m_n_pages = 1;
    m_v_offset = 0.0;
    m_last_chapter_date = 0;
    m_flag_chapter_round = DiaryElement::ET_CHAPTER;    // start with chapters

    m_chapters = Diary::d->get_current_chapter_ctg();
    m_iter_chapter = m_chapters->rbegin();

    m_parser.m_max_thumbnail_width = ( print_context->get_width() / 2 );

    // SETTING ENTRY ITERATORS
    m_iter_entry = Diary::d->get_entries().rbegin();
    if( m_opt_one_entry )
    {
        if( AppWindow::p->panel_main->get_cur_elem_type() != DiaryElement::ET_ENTRY )
            throw LIFEO::Error( "Current element is not an entry. Printing aborted." );

        m_iter_entry_end = EntryIterReverse( Diary::d->get_entries().find(
                AppWindow::p->panel_main->get_cur_elem()->get_date_t() ) );
        m_iter_entry = m_iter_entry_end;
        m_iter_entry--;
    }
    else
    {
        m_iter_entry_end = Diary::d->get_entries().rend();
    }
}

void
PrintOpr::set_hide_comments( bool flag_hide )
{
    m_parser.option_hide_comments = flag_hide;
}

Gtk::Widget*
PrintOpr::on_create_custom_widget()
{
    Gtk::Grid *grid_main( new Gtk::Grid );
    Gtk::Box *vbox_extent( Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_VERTICAL, 5 ) ) );
    Gtk::RadioButtonGroup rgroup_extent;
    m_rbutton_entry = ( Gtk::manage(
            new Gtk::RadioButton( rgroup_extent, _( "Only Current Entry" ) ) ) );
    m_rbutton_filtered = ( Gtk::manage(
            new Gtk::RadioButton( rgroup_extent, _( "All Filtered Entries" ) ) ) );
    m_rbutton_diary = ( Gtk::manage(
            new Gtk::RadioButton( rgroup_extent, _( "Entire Diary" ) ) ) );
    Gtk::Frame *frame_extent( create_frame( _( "What to Print?" ), *vbox_extent ) );

    Gtk::Box *vbox_font( Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_VERTICAL, 5 ) ) );
    m_fontbutton = Gtk::manage( new Gtk::FontButton( ThemeSystem::get()->font.to_string() ) );
    m_check_use_entry_font = Gtk::manage(
            new Gtk::CheckButton( _( "Use Theme Fonts in Entries" ) ) );
    m_check_justify = Gtk::manage( new Gtk::CheckButton( _( "Justify Text on the Page" ) ) );
    Gtk::Frame *frame_font( create_frame( _( "Text Options" ), *vbox_font ) );

    grid_main->set_border_width( 8 );
    grid_main->set_column_homogeneous( true );

    vbox_extent->set_border_width( 5 );
    vbox_extent->pack_start( *m_rbutton_entry, Gtk::PACK_SHRINK );
    vbox_extent->pack_start( *m_rbutton_filtered, Gtk::PACK_SHRINK );
    vbox_extent->pack_start( *m_rbutton_diary, Gtk::PACK_SHRINK );
    grid_main->attach( *frame_extent, 0, 0, 1, 2 );

    vbox_font->set_border_width( 5 );
    vbox_font->pack_start( *m_fontbutton, Gtk::PACK_SHRINK );
    vbox_font->pack_start( *m_check_use_entry_font, Gtk::PACK_SHRINK );
    vbox_font->pack_start( *m_check_justify, Gtk::PACK_SHRINK );
    grid_main->attach( *frame_font, 1, 0, 1, 1 );

    // could not figure out how to use Gtk::Builder in conjunction with PrintDialog
    // using a dedicated ui file may be one way of doing it
//    Lifeograph::get_builder()->get_widget( "vbox_printing", s_vbox_extras );
//    Lifeograph::get_builder()->get_widget( "rbutton_print_entry", m_rbutton_entry );
//    Lifeograph::get_builder()->get_widget( "rbutton_print_filtered", m_rbutton_filtered );
//    Lifeograph::get_builder()->get_widget( "rbutton_print_diary", m_rbutton_diary );

    grid_main->show_all_children();

    return grid_main;
}

void
PrintOpr::on_custom_widget_apply( Gtk::Widget* widget )
{
    m_opt_one_entry = m_rbutton_entry->get_active();
    m_opt_entire_diary = m_rbutton_diary->get_active();

    m_font = Pango::FontDescription( m_fontbutton->get_font_name() );
    m_parser.option_use_theme_font = m_check_use_entry_font->get_active();
    m_opt_justify = m_check_justify->get_active();
}

Glib::RefPtr< PrintOpr >
PrintOpr::create()
{
    return Glib::RefPtr< PrintOpr >( new PrintOpr() );
}

void
PrintOpr::on_done( Gtk::PrintOperationResult result )
{
    //Printing is "done" when the print data is spooled.

    if( result == Gtk::PRINT_OPERATION_RESULT_ERROR )
    {
        Gtk::MessageDialog err_dialog( *AppWindow::p, "Printing failed", 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 = get_print_settings();
    }

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

void
PrintOpr::on_status_changed()
{
    if( is_finished() )
    {
        print_info( "Print job completed" );
    }
    else
    {
        print_info( get_status_string() );
    }
}

void
PrintOpr::show_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( *AppWindow::p, 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;
}

inline void
PrintOpr::add_page()
{
    m_content.push_back( new PrintablePageBreak );
    m_n_pages++;
    m_v_offset = 0.0;
}

void
PrintOpr::handle_text( const Glib::RefPtr< Gtk::PrintContext >& print_context )
{
    m_layout = print_context->create_pango_layout();

    const double width = print_context->get_width();
    const double height = print_context->get_height();
    double total_line_h = 0.0;

    m_layout->set_width( static_cast< int >( width * Pango::SCALE ) );
    m_layout->set_font_description( m_font );
    m_layout->set_markup( m_marked_up_text );
    // cover page is center aligned
    m_layout->set_alignment( m_flag_align_center ? Pango::ALIGN_CENTER : Pango::ALIGN_LEFT );
    m_layout->set_justify( m_opt_justify );

    PRINT_DEBUG( m_marked_up_text );

    const int last_line( m_layout->get_line_count() - 1 );
    int line_begin( 0 );

    for( int line = 0; line <= last_line; line++ )
    {
        Pango::Rectangle ink_rect, logical_rect;
        m_layout->get_line( line )->get_extents( ink_rect, logical_rect );
        const double line_height = logical_rect.get_height() / 1024.0;

        if( ( m_v_offset + total_line_h + line_height ) > height )
        {
            line--;
            if( line < 0 )  // if first line is higher than the remaining space on the page
            {
                // at least one line should fit into the page
                if( m_v_offset == 0.0 )
                {
                    print_error( "Cannot print, line is too big for the page" );
                    line++;
                }
                else
                    add_page();
                continue;
            }

            m_content.push_back( new PrintableText( m_layout, m_v_offset, line_begin, line ) );

            add_page();
            line_begin = line + 1;
            total_line_h = 0.0;
        }
        else if( line == last_line )
        {
            m_content.push_back(
                    new PrintableText( m_layout, m_v_offset, line_begin, line ) );
            m_v_offset += total_line_h;
        }
        else
            total_line_h += line_height;
    }
}

void
PrintOpr::handle_entry_break( const Glib::RefPtr< Gtk::PrintContext >& print_context )
{
    const double break_height = print_context->get_height() / 10;
    const double height = print_context->get_height();

    // TODO: also consider entry header height

    if( m_v_offset + break_height > height )
        add_page();
    else
    {
        m_content.push_back( new PrintableEntryBreak );
        m_v_offset += break_height;
    }
}

void
PrintOpr::handle_image( const Glib::RefPtr< Gtk::PrintContext >& print_context )
{
    const int image_height = m_parser.m_pango_pixbuf->get_height();
    const double height = print_context->get_height();

    if( m_v_offset + image_height > height )
        add_page();

    m_content.push_back( new PrintableImage( m_parser.m_pango_pixbuf, m_v_offset ) );

    m_v_offset += image_height;
}

Printable::Type
PrintOpr::get_more()
{
    Chapter* chapter( NULL );
    Entry* entry( NULL );
    Printable::Type return_type( Printable::PT_PAGE_BREAK );

    m_marked_up_text.clear();

    // diary title:
    if( m_content.empty() && m_opt_one_entry == false )
    {
        Date today( Date::get_today() );

        m_marked_up_text = Glib::ustring::compose(
                "\n\n\n<span size='xx-large'>%1</span>\n%2",
                Glib::Markup::escape_text( Diary::d->get_name() ),
                today.format_string() );
        m_flag_align_center = true;
        return Printable::PT_PAGE_BREAK;
    }
    else
        m_flag_align_center = false;

    // FIXME: empty chapters after the last entry are not printed
    while( m_iter_entry != m_iter_entry_end )
    {
        entry = m_iter_entry->second;
        m_parser.set( entry );

        // CHAPTER
        while( !m_opt_one_entry )
        {
            // CHAPTER CTG--TOPIC SWITCH
            if( m_iter_chapter == m_chapters->rend() )
            {
                if( m_flag_chapter_round == DiaryElement::ET_CHAPTER )
                {
                    m_chapters = Diary::d->get_groups();
                    m_iter_chapter = m_chapters->rbegin();
                    m_flag_chapter_round = DiaryElement::ET_GROUP;
                    continue;   // to check if rbegin() == rend()
                }
                else if( m_flag_chapter_round == DiaryElement::ET_GROUP )
                {
                    m_chapters = Diary::d->get_topics();
                    m_iter_chapter = m_chapters->rbegin();
                    m_flag_chapter_round = DiaryElement::ET_TOPIC;
                    continue;   // to check if rbegin() == rend()
                }
                else
                    break;
            }

            chapter = m_iter_chapter->second;

            if( entry->get_date() >= chapter->get_date() &&
                chapter->get_date_t() > m_last_chapter_date )
            {
                if( m_content.back()->get_type() != Printable::PT_PAGE_BREAK )
                    return Printable::PT_PAGE_BREAK; // page break before chapter

                if( ! chapter->get_date().is_hidden() )
                {
                    m_marked_up_text += Glib::ustring::compose(
                            "<big>%1</big>\n", chapter->get_date().format_string() );
                }

                m_marked_up_text += Glib::ustring::compose(
                        "<big><big><b>%1</b></big></big>\n\n\n",
                        Glib::Markup::escape_text( chapter->get_name() ) );

                m_last_chapter_date = chapter->get_date_t();
            }
            else
                break;

            ++m_iter_chapter;
        }

        // ENTRY
        if( m_opt_entire_diary || entry->get_filtered_out() == false || m_opt_one_entry )
        {
            return_type = m_parser.parse();
            m_marked_up_text += m_parser.m_pango_text;
            if( return_type == Printable::PT_ENTRY_BREAK )
                ++m_iter_entry;

            return return_type;
        }

        ++m_iter_entry;
    }

    return Printable::PT_END;
}

void
PrintOpr::on_begin_print( const Glib::RefPtr< Gtk::PrintContext >& print_context )
{
    clear_content();
    init_variables( print_context );

    // PROCESSING THE MATERIAL TO PRINT
    bool more( true );
    do
    {
        switch( get_more() )
        {
//            case Printable::PT_TEXT:
//                handle_text( print_context );
//                break;
            case Printable::PT_ENTRY_BREAK:
                if( !m_marked_up_text.empty() )   // if there is any unprocessed text
                    handle_text( print_context );
                handle_entry_break( print_context );
                break;
            case Printable::PT_IMAGE:
                if( !m_marked_up_text.empty() )   // if there is any unprocessed text
                    handle_text( print_context );
                handle_image( print_context );
                break;
            case Printable::PT_PAGE_BREAK:
                if( !m_marked_up_text.empty() )   // if there is any unprocessed text
                    handle_text( print_context );
                add_page();
                break;
            case Printable::PT_END:
                // no unprocessed text should be left when this point is reached
                more = false;
                break;
            default:
                break;
        }
    }
    while( more );

    set_n_pages( m_n_pages );
}

void
PrintOpr::on_draw_page( const Glib::RefPtr< Gtk::PrintContext >& print_context, int page_no )
{
    int i( 0 );

    for( Printable* printable : m_content )
    {
        if( i == page_no )
            printable->render( print_context );

        if( printable->get_type() == Printable::PT_PAGE_BREAK )
            i++;

        if( i > page_no )
            break;
    }
}

// PANGO ENTRY PARSER ==============================================================================
void
EntryParserPango::set( const Entry* entry )
{
    m_pango_text.clear();
    if( m_ptr2entry != entry )
    {
        m_pango_begin = 0;  // do not reset when continuing
        m_ptr2entry = entry;
    }
}

Printable::Type
EntryParserPango::parse()
{
    m_pango_return = Printable::PT_ENTRY_BREAK;
    m_pango_text.clear();

    if( m_pango_begin == 0 )
    {
        if( ! m_ptr2entry->get_date().is_hidden() )
        {
            m_pango_text = Glib::ustring::compose(
                    "<b>%1</b>\n", m_ptr2entry->get_date().format_string() );
        }

        if( option_use_theme_font && m_ptr2entry->get_theme_is_set() )
        {
            // TODO: health of the font should also be checked as it may crash the program
            m_pango_text += "<span font='";
            m_pango_text += m_ptr2entry->get_theme()->font.to_string();
            m_pango_text += "'>";
        }
    }

    EntryParser::parse( m_pango_begin, m_ptr2entry->get_text()->size() );

    if( option_use_theme_font && m_ptr2entry->get_theme_is_set() )
        m_pango_text += "</span>";

    return m_pango_return;
}

void
EntryParserPango::apply_regular()
{
    if( pos_regular < m_pos_start )
    {
        const Glib::ustring esc_text(
                m_ptr2entry->get_text()->substr( pos_regular, m_pos_start - pos_regular ) );
        m_pango_text.append( Glib::Markup::escape_text( esc_text ) );
    }
}

void
EntryParserPango::apply_markup( const Glib::ustring& tag_o, const Glib::ustring& tag_c,
                                int off_o, int off_c )
{
    // before adding any marked up text, last regular text chunk must be handled:
    begin_apply();  // in turn calls apply_regular()

    const Glib::ustring esc_text(
            m_ptr2entry->get_text()->substr( m_pos_start + off_o,
                                             m_pos_current - m_pos_start + off_c ) );
    m_pango_text += tag_o;
    m_pango_text += Glib::Markup::escape_text( esc_text );
    m_pango_text += tag_c;
}

void
EntryParserPango::apply_heading_end()
{
    const Glib::ustring esc_text( m_ptr2entry->get_text()->substr( 0, m_pos_current ) );
    m_pango_text += "<big><b>";
    switch( m_ptr2entry->get_status() & ES::FILTER_TODO )
    {
        case ES::TODO:
            m_pango_text += "[ ] ";
            break;
        case ES::PROGRESSED:
            m_pango_text += "[~] ";
            break;
        case ES::DONE:
            m_pango_text += "[+] ";
            break;
        case ES::CANCELED:
            m_pango_text += "[X] <s>";
            break;
    }
    m_pango_text += Glib::Markup::escape_text( esc_text );
    if( m_ptr2entry->get_status() & ES::CANCELED )
        m_pango_text += "</s>";
    m_pango_text += "</b></big>";
    pos_regular = m_pos_current;
}

void
EntryParserPango::apply_subheading()
{
    // before adding any marked up text, last regular text chunk must be handled:
    begin_apply();  // in turn calls apply_regular()
    pos_regular = m_pos_start;

    m_pango_text += "<big>";
}

void
EntryParserPango::apply_subheading_end()
{
    if( pos_regular < m_pos_current )
    {
        const Glib::ustring esc_text(
                m_ptr2entry->get_text()->substr( pos_regular, m_pos_current - pos_regular + 1 ) );
        m_pango_text += Glib::Markup::escape_text( esc_text );
        pos_regular = m_pos_current + 1;
    }
    m_pango_text += "</big>";
}

void
EntryParserPango::apply_bold()
{
    apply_markup( "<b>", "</b>", 1, -1 );
}

void
EntryParserPango::apply_italic()
{
    apply_markup( "<i>", "</i>", 1, -1 );
}

void
EntryParserPango::apply_strikethrough()
{
    apply_markup( "<s>", "</s>", 1, -1 );
}

void
EntryParserPango::apply_highlight()
{
    apply_markup( "<span bgcolor='yellow'>", "</span>", 1, -1 );
}

void
EntryParserPango::apply_comment()
{
    if( option_hide_comments )
        begin_apply();  // ignores comment
    else
        apply_markup( "<small><sup>", "</sup></small>", 1, -1 );
}

void
EntryParserPango::apply_check_ccl()
{
    const Glib::ustring esc_text(
            m_ptr2entry->get_text()->substr( pos_regular, m_pos_current - pos_regular - 3 ) );

    m_pango_text += Glib::Markup::escape_text( esc_text );
    m_pango_text += "<span font=\"Monospace\">";
    m_pango_text += m_ptr2entry->get_text()->substr( m_pos_current - 3, 3 );
    m_pango_text += "</span>";
    pos_regular = m_pos_current + 1;

    m_pango_text += " <s>";
}

void
EntryParserPango::apply_check_ccl_end()
{
    if( pos_regular < m_pos_current )
    {
        const Glib::ustring esc_text(
                m_ptr2entry->get_text()->substr( pos_regular, m_pos_current - pos_regular ) );
        m_pango_text += Glib::Markup::escape_text( esc_text );
        pos_regular = m_pos_current;
    }
    m_pango_text += "</s>";
}

void
EntryParserPango::apply_check_unf()
{
    const Glib::ustring esc_text(
            m_ptr2entry->get_text()->substr( pos_regular, m_pos_current - pos_regular - 3 ) );
    m_pango_text += Glib::Markup::escape_text( esc_text );
    m_pango_text += "<span font=\"Monospace\">";
    m_pango_text += m_ptr2entry->get_text()->substr( m_pos_current - 3, 3 );
    m_pango_text += "</span>";
    pos_regular = m_pos_current + 1;
}

void
EntryParserPango::apply_check_prg()
{
    const Glib::ustring esc_text(
            m_ptr2entry->get_text()->substr( pos_regular, m_pos_current - pos_regular - 3 ) );
    m_pango_text += Glib::Markup::escape_text( esc_text );
    m_pango_text += "<span font=\"Monospace\">";
    m_pango_text += m_ptr2entry->get_text()->substr( m_pos_current - 3, 3 );
    m_pango_text += "</span>";
    pos_regular = m_pos_current + 1;
}

void
EntryParserPango::apply_check_fin()
{
    const Glib::ustring esc_text(
            m_ptr2entry->get_text()->substr( pos_regular, m_pos_current - pos_regular - 3 ) );
    m_pango_text += Glib::Markup::escape_text( esc_text );
    m_pango_text += "<span font=\"Monospace\">";
    m_pango_text += m_ptr2entry->get_text()->substr( m_pos_current - 3, 3 );
    m_pango_text += "</span>";
    pos_regular = m_pos_current + 1;

    m_pango_text += " <span bgcolor='yellow'>";
}

void
EntryParserPango::apply_check_fin_end()
{
    if( pos_regular < m_pos_current )
    {
        const Glib::ustring esc_text(
                m_ptr2entry->get_text()->substr( pos_regular, m_pos_current - pos_regular ) );
        m_pango_text += Glib::Markup::escape_text( esc_text );
        pos_regular = m_pos_current;
    }
    m_pango_text += "</span>";
}

void
EntryParserPango::apply_link()
{
    int offset1( 0 ), offset2( 1 );
    bool flag_img( false );

    if( m_flag_hidden_link )
    {
        offset1 = pos_tab - m_pos_start + 1;
        offset2 = -offset1;
    }
    else if( m_pos_start > 0 )   // first line is reserved for title
    {
        // HANDLE RELATIVE LINKS
        if( m_word_last.find( "rel://" ) == 0 )
            m_word_last.replace( 0, 5, "file://" + Glib::path_get_dirname( Diary::d->get_path() ) );

        if( m_word_last.find( "file:///" ) == 0 &&
            // there can be no text on the same line as the image:
            get_char_at( m_pos_start - 1 ) == '\n' && m_char_current == '\n' )
        {
            try
            {
                m_pango_pixbuf = Lifeograph::get_thumbnail( Glib::filename_from_uri( m_word_last ),
                                                            m_max_thumbnail_width );
                m_pango_return = Printable::PT_IMAGE;
                m_pango_begin = m_pos_current;

                begin_apply();  // calls apply_regular()

                m_pos_end = 0;  // force parsing loop to finish

                flag_img = true;
            }
            catch( Glib::FileError &er )
            {
                print_error( "Link target not found" );
            }
            catch( Gdk::PixbufError &er )
            {
                PRINT_DEBUG( "Link is not an image" );
            }
        }
    }

    if( !flag_img )
        apply_markup( "<span color='blue' underline='single'>", "</span>", offset1, offset2 );
}

Wchar
EntryParserPango::get_char_at( int i )
{
    return m_ptr2entry->get_text()->at( i );
}
