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

    Copyright (C) 2007-2020 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 "../ui_entry.hpp"
#include "widget_entrylist.hpp"


using namespace LIFEO;


// WIDGET ENTRY LIST ===============================================================================
WidgetEntryList::WidgetEntryList( BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& )
:   Gtk::TreeView( cobject )
{
    m_TVC_main = Gtk::manage( new Gtk::TreeViewColumn );
    // CELL RENDERERS
    m_CRP_item_icon_primary = Gtk::manage( new Gtk::CellRendererPixbuf );
    Gtk::CellRendererText* cellr_text = Gtk::manage( new Gtk::CellRendererText );
    m_CRP_item_icon_secondary = Gtk::manage( new Gtk::CellRendererPixbuf );

    cellr_text->property_ellipsize() = Pango::ELLIPSIZE_END;
    if( Lifeograph::settings.small_lists )
        cellr_text->property_scale() = .90;
    cellr_text->set_fixed_height_from_font( 1 );

    m_TVC_main->pack_start( *m_CRP_item_icon_primary, false );
    m_TVC_main->pack_start( *cellr_text );
    m_TVC_main->pack_start( *m_CRP_item_icon_secondary, false );

    m_TVC_main->add_attribute( m_CRP_item_icon_primary->property_pixbuf(), ListData::colrec->icon );
    m_TVC_main->add_attribute( cellr_text->property_markup(), ListData::colrec->info );
    m_TVC_main->add_attribute( m_CRP_item_icon_secondary->property_pixbuf(),
                               ListData::colrec->icon2 );

    m_TVC_main->set_cell_data_func( *m_CRP_item_icon_secondary,
                                    sigc::mem_fun( this, &WidgetEntryList::cell_data_func_icon ) );

    // TODO we failed to accomplish fixed height mode with the second icon renderer
    //column->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED );

    m_treestore = Gtk::TreeStore::create( *ListData::colrec );

    if( Lifeograph::get_color_insensitive().empty() )
    {
        Lifeograph::set_color_insensitive( convert_gdkcolor_to_html(
                get_style_context()->get_color( Gtk::STATE_FLAG_INSENSITIVE ) ) );
    }

    append_column( *m_TVC_main );

    Glib::RefPtr< Gtk::CssProvider > css = Gtk::CssProvider::create();
    if( css->load_from_data(
            "treeview#elem_list { background-color: transparent } "
            "treeview#elem_list:selected { color: #bb3333; background-color: #dddddd } "
            "treeview#elem_list button { background: transparent } " ) )
        Gtk::StyleContext::add_provider_for_screen(
                Gdk::Screen::get_default(), css, GTK_STYLE_PROVIDER_PRIORITY_USER );

    get_selection()->signal_changed().connect(
            sigc::mem_fun( this, &WidgetEntryList::handle_selection_changed ) );
}

void
WidgetEntryList::cell_data_func_icon( Gtk::CellRenderer* cell,
                                      const Gtk::TreeModel::iterator& iter )
{
    Entry* elem( ( * iter )[ ListData::colrec->ptr ] );
    if( elem != nullptr )
    {
        bool favored( elem->is_favored() );
        cell->set_property( "visible", favored );
        if( favored )
            cell->set_property( "pixbuf", Lifeograph::icons->favorite_16 );
    }
}

WidgetEntryList::~WidgetEntryList()
{
}

void
WidgetEntryList::set_editable( bool flag_editable )
{
    m_flag_editable = flag_editable;

    if( flag_editable )
    {
        enable_model_drag_source( { Lifeograph::DTE_entry_row } );
        enable_model_drag_dest( { Lifeograph::DTE_entry_row,
                                  Lifeograph::DTE_text_plain,
                                  Lifeograph::DTE_theme },
                                Gdk::ACTION_MOVE );
    }
    else
    {
        unset_rows_drag_source();
        unset_rows_drag_dest();
    }
}

void
WidgetEntryList::clear()
{
    m_treestore->clear();
    Lifeograph::set_dragged_elem( nullptr );
    m_flag_last_action_out_of_expander = false;
}

void
WidgetEntryList::present_element( const DiaryElement* element )
{
    Gtk::TreePath path = element->m_list_data->treepath;
    Gtk::TreePath path_up = path;

    if( path_up.up() )
    {
        m_flag_last_action_out_of_expander = false;
        expand_to_path( path_up );
        m_flag_last_action_out_of_expander = true;
    }

    get_selection()->unselect_all();
    get_selection()->select( path );

    Gtk::TreePath path_b, path_e;
    get_visible_range( path_b, path_e );

    if( is_treepath_less( path, path_b ) || is_treepath_more( path, path_e ) )
    {
        PRINT_DEBUG( "path is not visible" );
        scroll_to_row( path, 0.05 );   // does nothing if already visible
    }
}

void
WidgetEntryList::expand_element( const DiaryElement* element ) // this function does not do much
{
    expand_to_path( element->m_list_data->treepath );
}

void
WidgetEntryList::go_up( bool flag_entry_operation )
{
    Entry* entry( AppWindow::p->UI_entry->get_cur_entry() );

    if( entry == nullptr )
        return;
    else
    if( entry->get_type() < DiaryElement::ET_DIARY ) // not list element
        return;

    Gtk::TreePath path( entry->m_list_data->treepath );

    do
    {
        if( ! path.prev() )
        {
            if( path.size() > 1 )
                path.up();
            else    // diary
            if( make_path_deeper_last( path ) )
                make_path_deeper_last( path ); // go still deeper if possible:
        }
        else
            make_path_deeper_last( path );

        entry = ( * m_treestore->get_iter( path ) )[ ListData::colrec->ptr ];
    }
    // repeat until an entry is found if that is what we are looking for
    // BEWARE: do not set this flag when there is no entry in the list
    while( flag_entry_operation && entry->get_type() != DiaryElement::ET_ENTRY );

    PRINT_DEBUG( "previous path: " + path.to_string() );

    Gtk::TreeRow row( * m_treestore->get_iter( path ) );
    AppWindow::p->show_entry( row[ ListData::colrec->ptr ] );
}

void
WidgetEntryList::go_down( bool flag_entry_operation )
{
    Entry* entry( AppWindow::p->UI_entry->get_cur_entry() );

    if( entry == nullptr )
        return;
    else
    if( entry->get_type() < DiaryElement::ET_DIARY ) // not list element
        return;

    Gtk::TreePath path( entry->m_list_data->treepath );

    do
    {
        if( ! m_treestore->get_iter( path )->children().empty() )
            path.down();
        else
        if( ! move_path_next( path ) )
        {
            while( path.size() > 1 )
            {
                path.up();
                if( move_path_next( path ) )
                    break;
            }
        }

        entry = ( * m_treestore->get_iter( path ) )[ ListData::colrec->ptr ];
    }
    // repeat until an entry is found if that is what we are looking for
    // BEWARE: do not set this flag when there is no entry in the list
    while( flag_entry_operation && entry->get_type() != DiaryElement::ET_ENTRY );

    PRINT_DEBUG( "next path: " + path.to_string() );

    Gtk::TreeRow row( * m_treestore->get_iter( path ) );
    AppWindow::p->show_entry( row[ ListData::colrec->ptr ] );
}

void
WidgetEntryList::set_sorting_criteria( SortCriterion sc )
{
    switch( sc & SoCr_FILTER_CRTR )
    {
        default: // future-proofing
        case SoCr_DATE:
            m_treestore->set_default_sort_func(
                    sigc::mem_fun( this, &WidgetEntryList::sort_by_date ) );
            break;
        case SoCr_NAME:
            m_treestore->set_default_sort_func(
                    sigc::mem_fun( this, &WidgetEntryList::sort_by_name ) );
            break;
        case SoCr_SIZE_C:
            m_treestore->set_default_sort_func(
                    sigc::mem_fun( this, &WidgetEntryList::sort_by_size ) );
            break;
        case SoCr_CHANGE:
            m_treestore->set_default_sort_func(
                    sigc::mem_fun( this, &WidgetEntryList::sort_by_change ) );
            break;
    }
}

// not used now but may be used in the future
Gtk::TreePath
WidgetEntryList::find_element( const DiaryElement* element ) const
{
    Gtk::TreeIter iter_diary( m_treestore->children().begin() );
    if( element == ( *iter_diary )[ ListData::colrec->ptr ] )
        return m_treestore->get_path( iter_diary );

    for( Gtk::TreeIter iter = ( *iter_diary )->children().begin();
         iter != ( *iter_diary )->children().end();
         ++iter )
    {
        const DiaryElement *list_elem( ( *iter )[ ListData::colrec->ptr ] );
        if( element->get_id() == list_elem->get_id() )
        {
            return m_treestore->get_path( iter );
        }
        else
        {
            if( element->get_type() == DiaryElement::ET_ENTRY &&
                list_elem->get_type() != DiaryElement::ET_ENTRY )
                if( element->get_date() > list_elem->get_date() )
                    for( Gtk::TreeIter itr2 = ( *iter )->children().begin();
                         itr2 != ( *iter )->children().end();
                         ++itr2 )
                    {
                        if( element == ( *itr2 )[ ListData::colrec->ptr ] )
                            return m_treestore->get_path( itr2 );
                    }
        }
    }

    return m_treestore->get_path( m_treestore->children().begin() );
}

bool
select_true( const Glib::RefPtr< Gtk::TreeModel >&, const Gtk::TreePath&, bool )
{
    return true;
}

bool
select_false( const Glib::RefPtr< Gtk::TreeModel >&, const Gtk::TreePath&, bool )
{
    return false;
}

bool
WidgetEntryList::on_button_press_event( GdkEventButton* event )
{
    Lifeograph::set_dragged_elem( nullptr );

    Gtk::TreePath path;
    Column* col;
    int x, y;
    if( get_path_at_pos( ( int ) event->x, ( int ) event->y, path, col, x, y ) )
    {
        Gtk::TreeRow row{ * m_treestore->get_iter( path ) };

        if( get_selection()->is_selected( path ) && get_selection()->count_selected_rows() > 1 &&
            !( event->state & ( Gdk::CONTROL_MASK | Gdk::SHIFT_MASK ) ) )
        {
            Lifeograph::set_dragged_elem( &m_multiple_entries );
            get_selection()->set_select_function( sigc::ptr_fun( &select_false ) );
        }
        else
        {
            Lifeograph::set_dragged_elem( row[ ListData::colrec->ptr ] );
            get_selection()->set_select_function( sigc::ptr_fun( &select_true ) );
        }
    }

    m_flag_last_action_out_of_expander = is_point_out_of_expander( path, x );

    return Gtk::TreeView::on_button_press_event( event );
}

Gdk::Rectangle
WidgetEntryList::get_item_rect( const DiaryElement* elem ) const
{
    Gdk::Rectangle rect;
    get_cell_area( elem->m_list_data->treepath, *m_TVC_main, rect );
    return rect;
}

bool
WidgetEntryList::is_point_out_of_expander( const Gtk::TreePath& path, int pt_x ) const
{
    Gdk::Rectangle rect;

    get_cell_area( path, *m_TVC_main, rect );

    PRINT_DEBUG( "pt_x = ", pt_x, " | width = ", m_TVC_main->get_width(), " | cell w = ", rect.get_width() );

    return( pt_x > ( m_TVC_main->get_width() - rect.get_width() - 1 ) );
}

bool
WidgetEntryList::on_button_release_event( GdkEventButton* event )
{
    get_selection()->set_select_function( sigc::ptr_fun( &select_true ) );

    if( not( event->state & ( Gdk::CONTROL_MASK | Gdk::SHIFT_MASK ) ) &&
        m_flag_last_action_out_of_expander )
    {
        Gtk::TreePath   path;
        Entry*          entry{ nullptr };

        if( get_path_at_pos( ( int ) event->x, ( int ) event->y, path ) )
        {
            Gtk::TreeRow row{ * m_treestore->get_iter( path ) };
            entry = row[ ListData::colrec->ptr ];
        }

        if( event->button == 1 && entry )
        {
            if( get_selection()->count_selected_rows() > 1 ) // multiple rows selected
            {
                get_selection()->unselect_all();
                get_selection()->select( path );
            }
            AppWindow::p->UI_entry->show( entry );
            AppWindow::p->UI_extra->set_entry( entry );
        }
        else
        if( event->button == 3 && ( m_flag_editable || entry ) )
        {
            AppWindow::p->UI_diary->show_entry_Po( entry, get_rectangle_from_event( event ) );
        }
    }

    Lifeograph::set_dragged_elem( nullptr );

    return Gtk::TreeView::on_button_release_event( event );
}

bool
WidgetEntryList::on_key_press_event( GdkEventKey* event )
{
    if( event->state == 0 )
    {
        switch( event->keyval )
        {
            case GDK_KEY_Home:
                AppWindow::p->show_entry( Diary::d->get_startup_entry() );
                return true;
            case GDK_KEY_End:
                return true;
            case GDK_KEY_Up:
                go_up( false );
                return true;
            case GDK_KEY_Down:
                go_down( false );
                return true;
        }
    }
    return Gtk::TreeView::on_key_press_event( event );
}

void
WidgetEntryList::on_drag_begin( const Glib::RefPtr< Gdk::DragContext >& dc )
{
    if( Lifeograph::get_dragged_elem() != nullptr )
    {
        get_selection()->set_select_function( sigc::ptr_fun( &select_true ) );

        //Gtk::TreeView::on_drag_begin( context );
        dc->set_icon( Lifeograph::get_dragged_elem()->get_icon32(), 0, 0 );
    }
}

void
WidgetEntryList::on_drag_end( const Glib::RefPtr< Gdk::DragContext >& dc )
{
    PRINT_DEBUG( "WidgetEntryList::on_drag_end()" );
    unset_drag_dest_row();
    Gtk::TreeView::on_drag_end( dc );
    Lifeograph::set_dragged_elem( nullptr );
}

bool
WidgetEntryList::on_drag_motion( const Glib::RefPtr< Gdk::DragContext >& dc,
                                 int x, int y, guint time )
{
    const auto&& operation_type{ drag_dest_find_target( dc ) };

    if( operation_type == "NONE" )
        return false;

    // AUTOSCROLL
    Gdk::Rectangle rect;
    get_visible_rect( rect );
    const double height{ double( rect.get_height() ) };
    const double range{ height / 12.0 };
    if( y < range )
    {
        int new_y{ int( rect.get_y() - ( 70.0 * ( ( range - double( y ) ) / range ) ) ) };
        if( new_y < 0 )
            new_y = 0;

        scroll_to_point( -1, new_y );
    }
    else
    if( y > ( height - range ) )
    {
        int new_y{ int( rect.get_y() + ( 70.0 * ( ( double( y ) - height + range ) / range ) ) ) };
        scroll_to_point( -1, new_y );
    }

    // EVALUATE THE HOVERED ELEMENT
    Gtk::TreePath drop_path;

    if( get_dest_row_at_pos( x, y, drop_path, m_drop_pos ) )
    {
        bool             retval { false };
        auto             row    { * m_treestore->get_iter( drop_path ) };
        Entry*           source { dynamic_cast< Entry* >( Lifeograph::get_dragged_elem() ) };
        Entry*           target { row[ ListData::colrec->ptr ] };
        Gdk::DragAction  action { Gdk::ACTION_MOVE };

        if( ! target )
            return false;
        //else
        PRINT_DEBUG( "target=", target->get_name() );
        if( operation_type == TE_TEXT_PLAIN )
        {
            retval = true;
            action = Gdk::ACTION_COPY;
            m_drop_pos = Gtk::TREE_VIEW_DROP_INTO_OR_AFTER;
        }
        else
        if( operation_type == TE_THEME )
        {
            retval = true;
            action = Gdk::ACTION_COPY;
            m_drop_pos = Gtk::TREE_VIEW_DROP_INTO_OR_AFTER;
        }
        else
        if( !source || target == source ||
            target->get_date().get_level() + source->get_descendant_depth() > 3 )
            return false;
        else
        {
            switch( source->get_type() )
            {
                case DiaryElement::ET_ENTRY:
                {
                    if( target->is_ordinal() == false )
                        m_drop_pos = Gtk::TREE_VIEW_DROP_BEFORE;
                    else
                    if( target->get_type() == DiaryElement::ET_CHAPTER )
                        m_drop_pos = Gtk::TREE_VIEW_DROP_AFTER;
                    else
                    if( target->get_date().get_level() + source->get_descendant_depth() == 3 )
                    {   // cannot be moved into
                        if( m_drop_pos == Gtk::TREE_VIEW_DROP_INTO_OR_BEFORE )
                            m_drop_pos = Gtk::TREE_VIEW_DROP_BEFORE;
                        else
                        if( m_drop_pos == Gtk::TREE_VIEW_DROP_INTO_OR_AFTER )
                            m_drop_pos = Gtk::TREE_VIEW_DROP_AFTER;
                    }
                    retval = true;
                    break;
                }
                case DiaryElement::ET_MULTIPLE_ENTRIES:
                    retval = ( target->get_type() == DiaryElement::ET_CHAPTER );
                    m_drop_pos = Gtk::TREE_VIEW_DROP_AFTER;
                    break;
                case DiaryElement::ET_CHAPTER:
                    retval = ( target->get_type() == DiaryElement::ET_ENTRY &&
                               target->get_date().is_ordinal() == false &&
                               target->get_date().get_pure() !=
                                        Lifeograph::get_dragged_elem()->get_date().get_pure() );
                    m_drop_pos = Gtk::TREE_VIEW_DROP_BEFORE;
                    break;
                default:
                    break;
            }
        }

        if( retval )
        {
            m_drop_target_last = target;
            dc->drag_status( action, time );
            set_drag_dest_row( drop_path, m_drop_pos );
            return true;
        }
    }

    return false;
}

void
WidgetEntryList::on_drag_data_received( const Glib::RefPtr<Gdk::DragContext>& dc,
                                        int, int, const Gtk::SelectionData& sel_data,
                                        guint, guint time )
{
    if( !m_drop_target_last )
    {
        dc->drag_finish( false, false, time );
        return;
    }

    const auto&& operation_type{ drag_dest_find_target( dc ) };
    const date_t date_tgt{ m_drop_target_last->get_date_t() };
    //auto&& tl = dc->list_targets(); // better keep this ready for debugging in the future

    if( operation_type == TE_TEXT_PLAIN ) // TEXT ONTO ENTRY/CHAPTER
    {
        m_drop_target_last->insert_text( Ustring::npos, sel_data.get_text() ); // npos: append mode
        AppWindow::p->UI_diary->refresh_row( m_drop_target_last );

        dc->drag_finish( true, false, time );
        return;
    }
    else if( operation_type == TE_THEME )
    {
        m_drop_target_last->set_theme( dynamic_cast< Theme* >( Lifeograph::get_dragged_elem() ) );
        if( AppWindow::p->UI_entry->is_cur_entry( m_drop_target_last ) )
            AppWindow::p->UI_entry->refresh_theme();

        dc->drag_finish( true, false, time );
        return;
    }
    else if( operation_type == TE_ENTRY_ROW )
    {
        switch( Lifeograph::get_dragged_elem()->get_type() )
        {
            case DiaryElement::ET_ENTRY:
                // ENTRY ONTO CHAPTER
                if( m_drop_target_last->get_type() == DiaryElement::ET_CHAPTER )
                {
                    dc->drag_finish( true, false, time );
                    Entry* entry{ dynamic_cast< Entry* >( Lifeograph::get_dragged_elem() ) };
                    Chapter* chapter{ dynamic_cast< Chapter* >( m_drop_target_last ) };
                    Diary::d->set_entry_date( entry, chapter->get_free_order() );
                    AppWindow::p->UI_entry->refresh_title();
                    AppWindow::p->UI_diary->update_entry_list();
                    return;
                }
                // ENTRY ONTO ENTRY
                else
                {
                    dc->drag_finish( true, false, time );
                    Entry*       entry{ dynamic_cast< Entry* >( Lifeograph::get_dragged_elem() ) };
                    const date_t date_src{ entry->get_date_t() };

                    switch( m_drop_pos )
                    {
                        case Gtk::TREE_VIEW_DROP_BEFORE:
                            // if source is a little brother of target, special care is needed:
                            if( Date::is_sibling( date_src, date_tgt ) && date_src < date_tgt )
                                Diary::d->set_entry_date( entry, Date::get_prev_date( date_tgt ) );
                            else
                                Diary::d->set_entry_date( entry, date_tgt );
                            break;
                        case Gtk::TREE_VIEW_DROP_INTO_OR_BEFORE:
                        case Gtk::TREE_VIEW_DROP_INTO_OR_AFTER:
                            Diary::d->set_entry_date(
                                    entry, Diary::d->get_available_order_sub( date_tgt ) );
                            break;
                        case Gtk::TREE_VIEW_DROP_AFTER:
                            if( Date::is_sibling( date_src, date_tgt ) && date_src < date_tgt )
                                Diary::d->set_entry_date( entry, date_tgt );
                            else
                                Diary::d->set_entry_date( entry, Date::get_next_date( date_tgt ) );
                            break;
                    }
                    AppWindow::p->UI_entry->refresh_title();
                    AppWindow::p->UI_diary->update_entry_list();
                    return;
                }
                break;
            case DiaryElement::ET_MULTIPLE_ENTRIES:
                if( m_drop_target_last->get_type() == DiaryElement::ET_CHAPTER )
                {
                    dc->drag_finish( true, false, time );
                    AppWindow::p->UI_diary->do_for_each_selected_entry(
                            [ this ]( Entry* e )
                            {
                                move_entry_to_chapter(
                                        e, dynamic_cast< Chapter* >( m_drop_target_last ) );
                            } );

                    AppWindow::p->UI_entry->refresh_title();
                    AppWindow::p->UI_diary->update_entry_list();

                    return;
                }
                break;
            case DiaryElement::ET_CHAPTER:
                if( Diary::d->get_chapter_ctg_cur()->set_chapter_date(
                        dynamic_cast< Chapter* >( Lifeograph::get_dragged_elem() ), date_tgt ) )
                {
                    dc->drag_finish( true, false, time );
                    Diary::d->update_entries_in_chapters();
                    AppWindow::p->UI_entry->refresh_title();
                    AppWindow::p->UI_diary->update_entry_list();

                    return;
                }
                break;
            default:
                break;
        }
    }

    dc->drag_finish( false, false, time );
}

void
WidgetEntryList::handle_selection_changed()
{
    if( Lifeograph::is_internal_operations_ongoing() )
        return;

    m_multiple_entries.m_entries.clear();

    auto&&    sel{ get_selection() };
    auto&&    selected_rows{ sel->get_selected_rows() };

    for( Gtk::TreePath& path : selected_rows )
    {
        Gtk::TreeRow row( * m_treestore->get_iter( path ) );
        Entry* entry( row[ ListData::colrec->ptr ] );
        m_multiple_entries.m_entries.push_back( entry );
    }
}

void
WidgetEntryList::do_for_each_selected_entry( const sigc::slot< void, Entry* >& slot )
{
    for( Entry* entry : m_multiple_entries.m_entries )
    {
        slot( entry );
    }
}

bool
WidgetEntryList::on_test_expand_row( const Gtk::TreeIter& iter, const Gtk::TreePath& )
{
    return m_flag_last_action_out_of_expander;
}
bool
WidgetEntryList::on_test_collapse_row( const Gtk::TreeIter& iter, const Gtk::TreePath& )
{
    return m_flag_last_action_out_of_expander;
}

void
WidgetEntryList::on_row_expanded( const Gtk::TreeIter& iter, const Gtk::TreePath& )
{
    DiaryElement* element = ( * iter )[ ListData::colrec->ptr ];
    element->set_expanded( true );
}
void
WidgetEntryList::on_row_collapsed( const Gtk::TreeIter& iter, const Gtk::TreePath& )
{
    DiaryElement* element = ( * iter )[ ListData::colrec->ptr ];
    element->set_expanded( false );
}

void
WidgetEntryList::on_row_activated( const Gtk::TreePath& path, Gtk::TreeViewColumn* )
{
    m_flag_last_action_out_of_expander = false;

    if( row_expanded( path ) )
        collapse_row( path );
    else
        expand_to_path( path );

    m_flag_last_action_out_of_expander = true;
}

int
WidgetEntryList::sort_by_date( const Gtk::TreeModel::iterator& itr1,
                               const Gtk::TreeModel::iterator& itr2 )
{
    DiaryElement* item1 = ( *itr1 )[ ListData::colrec->ptr ];
    DiaryElement* item2 = ( *itr2 )[ ListData::colrec->ptr ];
    if( !( item1 && item2 ) )
        return 0;

    auto sc = Diary::d->get_sorting_criteria();
    int direction{ 1 };

    if( Date::is_same_kind( item1->get_date_t(), item2->get_date_t() ) )
    {
        if( item1->get_date().is_ordinal() )
            direction = ( ( sc & SoCr_FILTER_DIR ) == SoCr_ASCENDING ? -1 : 1 );
        else
            direction = ( ( sc & SoCr_FILTER_DIR_T ) == SoCr_ASCENDING_T ? -1 : 1 );
    }

    if( item1->get_date_t() > item2->get_date_t() )
        return -direction;
    else
    if( item1->get_date_t() < item2->get_date_t() )
        return direction;
    else
        return 0;
}

int
WidgetEntryList::sort_by_name( const Gtk::TreeModel::iterator& itr1,
                               const Gtk::TreeModel::iterator& itr2 )
{
    DiaryElement* item1 = ( *itr1 )[ ListData::colrec->ptr ];
    DiaryElement* item2 = ( *itr2 )[ ListData::colrec->ptr ];
    if( !( item1 && item2 ) )
        return 0;

    if( ( Diary::d->get_sorting_criteria() & SoCr_FILTER_DIR ) == SoCr_DESCENDING )
        return( item1->get_name().compare( item2->get_name() ) * -1 );
    else
        return( item1->get_name().compare( item2->get_name() ) );
}

int
WidgetEntryList::sort_by_size( const Gtk::TreeModel::iterator& itr1,
                               const Gtk::TreeModel::iterator& itr2 )
{
    DiaryElement* item1 = ( *itr1 )[ ListData::colrec->ptr ];
    DiaryElement* item2 = ( *itr2 )[ ListData::colrec->ptr ];
    if( !( item1 && item2 ) )
        return 0;

    const auto size1{ item1->get_size() };
    const auto size2{ item2->get_size() };
    if( size1 == size2 )
        return 0;

    if( ( Diary::d->get_sorting_criteria() & SoCr_FILTER_DIR ) == SoCr_DESCENDING )
        return( size2 > size1 ? 1 : -1 );
    else
        return( size1 > size2 ? 1 : -1 );
}

int
WidgetEntryList::sort_by_change( const Gtk::TreeModel::iterator& itr1,
                                 const Gtk::TreeModel::iterator& itr2 )
{
    Entry* item1 = ( *itr1 )[ ListData::colrec->ptr ];
    Entry* item2 = ( *itr2 )[ ListData::colrec->ptr ];
    if( !( item1 && item2 ) )
        return 0;

    const time_t date1{ item1->get_date_edited() };
    const time_t date2{ item2->get_date_edited() };
    if( date1 == date2 )
        return 0;

    if( ( Diary::d->get_sorting_criteria() & SoCr_FILTER_DIR ) == SoCr_DESCENDING )
        return( date2 > date1 ? 1 : -1 );
    else
        return( date1 > date2 ? 1 : -1 );
}

inline bool
WidgetEntryList::move_path_next( Gtk::TreePath& path )
{
    if( unsigned( path.back() ) <
            ( m_treestore->get_iter( path )->parent()->children().size() - 1 ) )
    {
        path.next();
        return true;
    }
    else
        return false;
}

inline bool
WidgetEntryList::make_path_deeper_last( Gtk::TreePath& path )
{
    if( ! m_treestore->get_iter( path )->children().empty() )
    {
        path.push_back( m_treestore->get_iter( path )->children().size() - 1 );
        return true;
    }
    return false;
}

// HELPER FUNCTION
void
WidgetEntryList::move_entry_to_chapter( Entry* entry, Chapter* chapter )
{
    Diary::d->set_entry_date( entry, chapter->get_free_order() );
}

const Glib::RefPtr< Gdk::Pixbuf >&
WidgetEntryList::MultipleEntries::get_icon32() const
{
    return Lifeograph::icons->entry_32;
}
