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

    Copyright (C) 2007-2026 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 LoG;


// WIDGET ENTRY LIST ===============================================================================
WidgetEntryList::WidgetEntryList( BaseObjectType* cobject, const Glib::RefPtr< Gtk::Builder >& )
:   Gtk::ListView( cobject )
{
    // Add the factory for the ListView's single column.
    auto factory = Gtk::SignalListItemFactory::create();

    factory->signal_setup().connect( sigc::mem_fun( *this, &WidgetEntryList::handle_setup_row ) );
    factory->signal_bind().connect( sigc::mem_fun( *this, &WidgetEntryList::handle_bind_row ) );
    factory->signal_unbind().connect( sigc::mem_fun( *this, &WidgetEntryList::handle_unbind_row ) );

    set_factory( factory );

    // SIGNALS
    signal_activate().connect( sigc::mem_fun( *this, &WidgetEntryList::handle_row_activated ) );

    // CONTROLLERS
    m_controller_key = Gtk::EventControllerKey::create();
    m_controller_key->signal_key_pressed().connect(
            sigc::mem_fun( *this, &WidgetEntryList::handle_key_press ), false );
    add_controller( m_controller_key );
}

Glib::RefPtr< Gio::ListStore< GObjectEntry > >
WidgetEntryList::create_model( const Glib::RefPtr< Glib::ObjectBase >& item )
{
    auto   gentry  { std::dynamic_pointer_cast< GObjectEntry >( item ) };
    auto   entry   { gentry ? gentry->m_p2entry->get_child_1st() : Diary::d->get_entry_1st() };
    auto   result  { Gio::ListStore< GObjectEntry >::create() };

    for( ; entry; entry = entry->get_next() )
    {
        if( entry->is_filtered_out_completely() != 'o' ) // visible or has_visible descendant
            result->append( GObjectEntry::create( entry ) );
    }

    // if result is empty, it's a leaf in the tree, i.e. an item without children.
    // returning an empty RefPtr (not a RefPtr with an empty StringList)
    // signals that the item is not expandable.
    return( result->get_n_items() > 0 ? result : Glib::RefPtr< Gio::ListStore< GObjectEntry > >{} );
}

void
WidgetEntryList::handle_setup_row( const Glib::RefPtr< Gtk::ListItem >& list_item )
{
    auto expander = Gtk::make_managed< Gtk::TreeExpander >();
    auto box      = Gtk::make_managed< Gtk::Box >( Gtk::Orientation::HORIZONTAL, 5 );
    auto image    = Gtk::make_managed< Gtk::Image >();
    auto label    = Gtk::make_managed< Gtk::Label >();
    auto I_lock   = Gtk::make_managed< Gtk::Image >();
    auto I_fav    = Gtk::make_managed< Gtk::Image >();

    // TODO: 3.2: we may add this as an option:
    // Pango::AttrList attrs;
    // auto&&          attrscale{ Pango::Attribute::create_attr_scale( 0.9 ) };
    // attrs.insert( attrscale );

    label->set_halign( Gtk::Align::START );
    label->set_hexpand( true );
    label->set_ellipsize( Pango::EllipsizeMode::MIDDLE );
    // label->set_attributes( attrs );
    I_lock->set_from_icon_name( "changes-prevent-symbolic" );
    box->set_margin_end( 5 );
    box->append( *image );
    box->append( *label );
    box->append( *I_lock );
    box->append( *I_fav );
    expander->set_child( *box );
    list_item->set_child( *expander );

    // CONTROLLERS
    auto gesture_click = Gtk::GestureClick::create();
    gesture_click->set_button( 3 ); // right click
    gesture_click->signal_released().connect(
            sigc::bind( sigc::mem_fun( *this, &WidgetEntryList::handle_menu_click ), list_item ),
            false );
    expander->add_controller( gesture_click );

    auto gesture_doubleclick = Gtk::GestureClick::create();
    gesture_doubleclick->set_button( 1 );
    gesture_doubleclick->signal_pressed().connect(
            sigc::bind( sigc::mem_fun( *this, &WidgetEntryList::handle_double_click ),
                        list_item ),
            false );
    expander->add_controller( gesture_doubleclick );

    // DRAG & DROP
    auto drag_source { Gtk::DragSource::create() };
    drag_source->signal_prepare().connect(
        sigc::bind( sigc::mem_fun( *this, &WidgetEntryList::handle_drag_prepare ),
                    drag_source, list_item ),
        false );
    drag_source->signal_drag_end().connect(
            [ this ]( const Glib::RefPtr< Gdk::Drag >&, bool )
            {
                m_id_dragged_entry = DEID::UNSET;
                m_drop_position = DropPosition::NONE;
                queue_draw();
            } );
    drag_source->set_actions(Gdk::DragAction::MOVE);
    expander->add_controller( drag_source );

    auto drop_target { Gtk::DropTarget::create( G_TYPE_INVALID,
                                                Gdk::DragAction::COPY|Gdk::DragAction::MOVE ) };

    drop_target->set_gtypes( { Glib::Value< Ustring >::value_type(),
                               Theme::GValue::value_type(),
                               Entry::GValue::value_type(),
                               EntrySelection::GValue::value_type() } );
    drop_target->signal_motion().connect(
            sigc::bind( sigc::mem_fun( *this, &WidgetEntryList::handle_drop_motion ),
                        expander, list_item ),
            false );
    drop_target->signal_leave().connect(
            [ this ]()
            {
                m_drop_position = DropPosition::NONE;
                queue_draw();
            } );
    drop_target->signal_drop().connect(
            sigc::bind( sigc::mem_fun( *this, &WidgetEntryList::handle_drop ), list_item ),
            false );
    expander->add_controller( drop_target );
}

// helper func:
void
update_extra_icons( std::shared_ptr< GObjectEntry >& gentry )
{
    const bool F_trashed  { gentry->m_p2entry->is_trashed() };
    const bool F_locked   { gentry->m_p2entry->is_locked() };
    const bool F_favorite { gentry->m_p2entry->is_favorite() };

    gentry->m_I_lock->set_visible( F_locked );
    gentry->m_I_fav->set_visible( F_favorite && !F_trashed );

    if( F_locked )
        gentry->m_I_lock->set( gentry->m_p2entry->get_icon_lock() ); // lock or trash icon

    if( F_favorite )
        gentry->m_I_fav->set( Lifeograph::icons->favorite_16 );
}

void
WidgetEntryList::handle_bind_row( const Glib::RefPtr< Gtk::ListItem >& list_item )
{
    // When TreeListModel::property_passthrough() is false, ListItem::get_item()
    // is a TreeListRow. TreeExpander needs the TreeListRow.
    auto row      { std::dynamic_pointer_cast< Gtk::TreeListRow >( list_item->get_item() ) };
    if( !row ) return;
    auto gentry   { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
    if( !gentry ) return;
    auto expander { dynamic_cast< Gtk::TreeExpander* >( list_item->get_child() ) };
    if( !expander ) return;

    expander->set_list_row( row );
    // expander->set_indent_for_icon( true ); // this is the default it seems

    m_map_rows[ gentry->m_p2entry->get_id() ] = row;

    // note that when an entry is collapsed, its sub-entries are also collapsed automatically
    if( gentry->m_p2entry->get_child_1st() )
    {
        gentry->m_connection_expand = row->property_expanded()
            .signal_changed().connect( std::bind( &WidgetEntryList::handle_expand_changed,
                                                  this,
                                                  row ) );
    }

    // WIDGET CONTENTS
    gentry->m_Bx = dynamic_cast< Gtk::Box* >( expander->get_child() );
    gentry->m_I = dynamic_cast< Gtk::Image* >( gentry->m_Bx->get_first_child() );
    gentry->m_I->set( gentry->m_p2entry->get_icon() );
    gentry->m_L = dynamic_cast< Gtk::Label* >( gentry->m_I->get_next_sibling() );
    gentry->m_L->set_markup( gentry->m_p2entry->get_list_str() );
    gentry->m_L->set_tooltip_markup( gentry->m_p2entry->get_tip_str() );
    if( gentry->m_p2entry->is_trashed() )
        gentry->m_L->set_sensitive( false );
    gentry->m_I_lock = dynamic_cast< Gtk::Image* >( gentry->m_L->get_next_sibling() );
    gentry->m_I_fav = dynamic_cast< Gtk::Image* >( gentry->m_Bx->get_last_child() );
    update_extra_icons( gentry );
}
void
WidgetEntryList::handle_unbind_row( const Glib::RefPtr< Gtk::ListItem >& list_item )
{
    auto row    { std::dynamic_pointer_cast< Gtk::TreeListRow >( list_item->get_item() ) };
    if( !row ) return;
    auto gentry { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };

    if( gentry && Diary::d->is_open() )
    {
        // PRINT_DEBUG( "handle_UNbind:", gentry->m_p2entry->get_name() );
        gentry->m_connection_expand.disconnect(); // needed to avoid accumulating connections

        // only erase if THIS row is still the one in the map:
        // --a late unbind after a re-bind should be ignored
        auto it = m_map_rows.find( gentry->m_p2entry->get_id() );
        if( it != m_map_rows.end() && it->second == row )
            m_map_rows.erase( gentry->m_p2entry->get_id() );
    }
}

void
WidgetEntryList::set_editable()
{
    m_F_editable = true;
    // AppWindow::p->UI_diary->update_entry_list(); // rebinds the rows --why was this needed?
}

void
WidgetEntryList::clear_and_disable()
{
    m_F_editable = false;
    set_model( Gtk::SingleSelection::create() );
    m_map_rows.clear();
    m_p2expander_last = nullptr;
}

void
WidgetEntryList::populate()
{
    Lifeograph::START_INTERNAL_OPERATIONS();

    clear_cut_entries( false ); // reset the cut entries at updates
    m_p2expander_last = nullptr;

    // create the root model:
    m_store_root = create_model();

    if( !m_store_root )
    {
        m_selection_model = Gtk::NoSelection::create();
        // we handle this separately because empty list case creates problems with multi model
        PRINT_DEBUG( "NO ITEMS IN THE LIST" );
    }
    else
    {
        // set list model and selection model:
        // passthrough must be false when Gtk::TreeExpander is used in the view
        m_TLM = Gtk::TreeListModel::create( m_store_root,
                                            sigc::mem_fun( *this, &WidgetEntryList::create_model ),
                                            false, /* passthrough */
                                            false  /* autoexpand */ );

        m_selection_model = Gtk::MultiSelection::create( m_TLM );

        // update expansion states of the rows by expanding the needed rows:
        for( unsigned int i = 0; i < m_TLM->property_n_items(); ++i )
        {
            auto row    { m_TLM->get_row( i ) };
            if( !row ) continue;
            auto gentry { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
            if( !gentry ) continue;

            if( gentry->m_p2entry->is_expanded() )
                row->set_expanded( true );
        }
    }

    m_selection_model->signal_selection_changed().connect(
            sigc::mem_fun( *this, &WidgetEntryList::handle_selection_changed ) );

    set_model( m_selection_model );

    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

void
WidgetEntryList::select( Entry* p2entry, const Entry* p2entry_exposed )
{
    m_selected_entries.clear();
    get_model()->unselect_all();
    if( m_p2expander_last )
    {
        m_p2expander_last->unset_name();
        m_p2expander_last = nullptr;
    }

    Lifeograph::START_INTERNAL_OPERATIONS();

    if( p2entry )
    {
        if( !p2entry_exposed || p2entry_exposed == p2entry )
        {
            const auto order { get_entry_order( p2entry_exposed ? p2entry_exposed : p2entry ) };
            if( order >= 0 )
                scroll_to( order, Gtk::ListScrollFlags::FOCUS | Gtk::ListScrollFlags::SELECT );
        }
        else
        {
            auto row { get_entry_row( p2entry_exposed ) };
            if( row )
            {
                auto gentry { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
                if( gentry )
                {
                    m_p2expander_last =
                        dynamic_cast< Gtk::TreeExpander* >( gentry->m_Bx->get_parent() );
                    m_p2expander_last->set_name( "parent_of_selected_hidden" );
                }
            }
        }
        m_selected_entries.insert( p2entry );
    }

    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

void
WidgetEntryList::scroll_to_entry( const Entry* p2entry )
{
    const auto order { get_entry_order( p2entry ) };

    Lifeograph::START_INTERNAL_OPERATIONS();
    scroll_to( order < 6 ? 0 : ( order - 5 ), Gtk::ListScrollFlags::NONE, {} );
    // NOTE: we cannot use ListScrollFlags::SELECT due to the overscrolling trick we use
    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

void
WidgetEntryList::present_entry_hard( const Entry* p2entry )
{
    bool F_expasion_done { false };
    // expand recursively:
    for( Entry* p2ep = p2entry->get_parent(); p2ep; p2ep = p2ep->get_parent() )
    {
        if( p2ep->is_expanded() ) // no need to go further
            break;
        else
        {
            F_expasion_done = true;
            p2ep->set_expanded( true );
        }
    }

    if( F_expasion_done )
        populate();

    scroll_to_entry( p2entry );
    select( const_cast< Entry* >( p2entry ) );
}

void
WidgetEntryList::present_entry( const Entry* p2entry )
{
    auto p2entry_exposed { p2entry->get_first_exposed_upwards() };
    scroll_to_entry( p2entry_exposed );
    select( const_cast< Entry* >( p2entry ), p2entry_exposed );
}

void
WidgetEntryList::refresh_row( const Entry* entry )
{
    if( Lifeograph::is_internal_operations_ongoing() || !m_TLM || !entry->is_exposed_in_list() )
        return;

    auto row    { get_entry_row( entry ) };
    if( !row ) return;
    auto gentry { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
    if( !gentry ) return;

    gentry->m_I->set( entry->get_icon() );
    gentry->m_L->set_markup( entry->get_list_str() );
    gentry->m_L->set_sensitive( !entry->is_trashed() );
    update_extra_icons( gentry );
}
// simpler version of the above:
void
WidgetEntryList::refresh_row_label( const Entry* entry )
{
    if( Lifeograph::is_internal_operations_ongoing() || !m_TLM || !entry->is_exposed_in_list() )
        return;

    auto row    { get_entry_row( entry ) };
    if( !row ) return;
    auto gentry { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
    if( !gentry ) return;

    gentry->m_L->set_markup( entry->get_list_str() );
    gentry->m_L->set_sensitive( !entry->is_trashed() );
}

void
WidgetEntryList::toggle_selected_collapsed()
{
    if( m_selected_entries.empty() ) return;

    Entry*  entry  { *m_selected_entries.begin() };
    if( !entry ) return;
    auto    row    { get_entry_row( entry ) };
    if( !row ) return;

    row->set_expanded( !row->get_expanded() );
}

// bool
// WidgetEntryList::add_entry( Entry* e )
// {
//     if( Entry* p2exposed_parent = e->get_first_exposed_upwards(); p2exposed_parent )
//     {
//         if( e->get_parent() == p2exposed_parent )
//         {
//             auto row_p    { get_entry_row( p2exposed_parent ) };
//             auto gentry_p { std::dynamic_pointer_cast< GObjectEntry >( row_p->get_item() ) };
//             gentry_p->children->append( GObjectEntry::create( e ) );
//             return true;
//         }
//         else // parents up to exposed parent has to be added:
//         {
//             // TODO...
//         }
//     }
//     else
//     {
//         if( Entry* p2entry_p = e->get_parent(); p2entry_p )
//         // whole descendance chain has to be added:
//         {
//             // TODO...
//         }
//         else
//         {
//             m_store_root->append( GObjectEntry::create( e ) );
//             return true;
//         }
//     }
//     return false;
// }

void
WidgetEntryList::remove_entry( Entry* e )
{
    using GObjEntryStore = Gio::ListStore< GObjectEntry >;
    auto row    { get_entry_row( e ) };
    auto row_p  { row->get_parent() };
    auto gentry { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
    auto store  { row_p ? std::dynamic_pointer_cast< GObjEntryStore >( row_p->get_children() )
                        : m_store_root };

    // very dumb solution but works:
    for( guint i = 0; i < store->get_n_items(); ++i )
    {
        if( store->get_item( i ) == gentry )
        {
            store->remove( i );
            break;
        }
    }
}

void
WidgetEntryList::handle_menu_click( int n_press, double x, double y,
                                    const Glib::RefPtr< Gtk::ListItem >& list_item )
{
    auto row   { std::dynamic_pointer_cast< Gtk::TreeListRow >( list_item->get_item() ) };
    if( !row ) return;
    auto gentry{ std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
    if( !gentry ) return;

    // auto event { m_gesture_click->get_last_event( m_gesture_click->get_current_sequence() ) };
    // if( !event->triggers_context_menu() ) return;

    // const bool F_ctrl_or_shift_pressed{
    //         bool( m_gesture_click->get_current_event()->get_modifier_state() &
    //               ( Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::SHIFT_MASK) ) };

    // if( !F_ctrl_or_shift_pressed )

    auto pt   { gentry->m_Bx->get_parent()->compute_point( *AppWindow::p,
                                                           Gdk::Graphene::Point( x, y ) ) };

    if( !get_model()->is_selected( row->get_position() ) )
        scroll_to( row->get_position(),
                   Gtk::ListScrollFlags::SELECT | Gtk::ListScrollFlags::FOCUS );

    AppWindow::p->UI_diary->show_Po_entry( gentry->m_p2entry,
                                           new Gdk::Rectangle( pt->get_x(), pt->get_y(), 0, 0 ) );
}
void
WidgetEntryList::handle_double_click( int n_press, double x, double y,
                                      const Glib::RefPtr< Gtk::ListItem >& list_item )
{
    if( n_press != 2 ) return;

    auto row   { std::dynamic_pointer_cast< Gtk::TreeListRow >( list_item->get_item() ) };
    if( !row ) return;
    auto gentry{ std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
    if( !gentry ) return;

    AppWindow::p->open_popup_dlg( gentry->m_p2entry );
}

bool
WidgetEntryList::handle_key_press( guint keyval, guint, Gdk::ModifierType state )
{
    if( keyval == GDK_KEY_v || keyval == GDK_KEY_V )
    {
        if( !m_F_editable || m_cut_entries.empty() ) return false;

        switch( int( state ) )
        {
            case int( Gdk::ModifierType::ALT_MASK ):
                m_drop_position = DropPosition::INTO;
                break;
            case int( Gdk::ModifierType::CONTROL_MASK ):
                m_drop_position = DropPosition::AFTER;
                break;
            case HELPERS::CTRL_SHIFT_MASK:
                m_drop_position = DropPosition::BEFORE;
                break;
            default: return false;
        }

        Entry* entry_receiving { *m_selected_entries.begin() };
        entry_receiving->get_diary()->move_entries( &m_cut_entries, entry_receiving,
                                                    m_drop_position );

        clear_cut_entries( false );
        m_drop_position = DropPosition::NONE;

        // AppWindow::p->UI_entry->refresh_extra_info();
        AppWindow::p->UI_diary->update_entry_list();

        return true;
    }

    if( state == Gdk::ModifierType::CONTROL_MASK )
    {
        switch( keyval )
        {
            case GDK_KEY_x:
            case GDK_KEY_X:
                if( !m_F_editable ) return false;
                mark_selected_entries_cut();
                return true;
            case GDK_KEY_Return:
                toggle_selected_collapsed();
                return true;
        }
    }
    else if( state == Gdk::ModifierType( 0 ) )
    {
        if( keyval == GDK_KEY_Up )
        {
            // go_up();
            m_F_inhibit_show_entry = true;
            // return true;
            return false;
        }
        else if( keyval == GDK_KEY_Down )
        {
            // go_down();
            m_F_inhibit_show_entry = true;
            // return true;
            return false;
        }
    }

    return false;
}

// DRAG & DROP
// void
// WidgetEntryList::handle_drag_begin( const Glib::RefPtr< Gdk::Drag >& drag,
//                                     const Glib::RefPtr< Gtk::DragSource >& drag_source,
//                                     const Glib::RefPtr< GObjectEntry >& gentry )
// {
// }

Glib::RefPtr< Gdk::ContentProvider >
WidgetEntryList::handle_drag_prepare( double x, double y,
                                      const Glib::RefPtr< Gtk::DragSource >& drag_source,
                                      const Glib::RefPtr< Gtk::ListItem >& list_item )
{
    if( !m_F_editable ) return {};

    auto row   { std::dynamic_pointer_cast< Gtk::TreeListRow >( list_item->get_item() ) };
    if( !row ) return {};
    auto gentry{ std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
    if( !gentry ) return {};

    // multiple selection case:
    if( m_selected_entries.size() > 1 && m_selected_entries.has_entry( gentry->m_p2entry ) )
    {
        EntrySelection::GValue value_multi;
        value_multi.init( value_multi.value_type() );
        value_multi.set( &m_selected_entries );

        drag_source->set_icon( lookup_default_icon( "entry_plus-16", 16 ), 0, 0 );
        m_id_dragged_entry = DEID::MULTIPLE;

        return Gdk::ContentProvider::create( value_multi );
    }
    else
    {
        LoG::Entry::GValue value_entry;
        value_entry.init( value_entry.value_type() );
        value_entry.set( gentry->m_p2entry );

        auto widget_paintable { Gtk::WidgetPaintable::create( *gentry->m_Bx ) };
        drag_source->set_icon( widget_paintable, 0, 0 );
        m_id_dragged_entry = gentry->m_p2entry->get_id();

        return Gdk::ContentProvider::create( value_entry );
    }
}

// void
// WidgetEntryList::handle_drag_end( const Glib::RefPtr< Gdk::Drag >& drag, bool )
// {
//     PRINT_DEBUG( "WidgetEntryList::on_drag_end()" );
// }

Gdk::DragAction
WidgetEntryList::handle_drop_motion( double x, double y,
                                     Gtk::TreeExpander* expander,
                                     const Glib::RefPtr< Gtk::ListItem >& list_item )
{
    auto pt       { expander->compute_point( *this, Gdk::Graphene::Point( x, 0.0 ) ) };
    auto pt_2     { expander->compute_point( *this, Gdk::Graphene::Point( 0.0, 0.0 ) ) };
    auto dp_new   { DropPosition::NONE };
    auto action   { m_id_dragged_entry == DEID::UNSET ? Gdk::DragAction::COPY
                                                      : Gdk::DragAction::MOVE };
    auto row      { std::dynamic_pointer_cast< Gtk::TreeListRow >( list_item->get_item() ) };
    if( !row ) return {};
    auto gentry   { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
    if( !gentry ) return {};
    Entry* target_entry { gentry->m_p2entry };

    // disable dropping onto itself:
    if( m_id_dragged_entry == target_entry->get_id() )
    {
        PRINT_DEBUG( "drop target is the same as the source");
        action = Gdk::DragAction( 0 );
    }
    else if( m_id_dragged_entry == DEID::MULTIPLE )
    {
        if( m_selected_entries.find( target_entry ) != m_selected_entries.end() )
            action = Gdk::DragAction( 0 );
    }

    if( m_line_height < 1 )
        m_line_height = expander->get_height();

    m_drop_coord_x = pt_2->get_x();

    // EVALUATE THE HOVERED ELEMENT
    if( action == Gdk::DragAction( 0 ) )
    {
        dp_new = DropPosition::NONE;
    }
    else if( pt->get_x() < OFFSET_DROP_INTO || m_id_dragged_entry == DEID::UNSET )
    {
        dp_new = DropPosition::INTO;
        m_drop_coord_y = pt->get_y();
        m_drop_rect_h = m_line_height;
    }
    else if( y < m_line_height * 0.5 )
    {
        dp_new = DropPosition::BEFORE;
        m_drop_coord_y = pt->get_y();
        m_drop_rect_h = 4.0;
    }
    else
    {
        dp_new = DropPosition::AFTER;
        m_drop_coord_y = pt->get_y() + m_line_height - 3;
        m_drop_rect_h = 4.0;
    }

    if( dp_new != m_drop_position )
    {
        m_drop_position = dp_new;
        queue_draw();
    }

    // ACTION TYPE
    return action;
}

void
WidgetEntryList::mark_selected_entries_cut()
{
    clear_cut_entries( true );

    for( Entry* e : m_selected_entries )
    {
        e->set_status_flag( ES::CUT, true );
        m_cut_entries.insert( e );
        refresh_row_label( e );
    }
}
void
WidgetEntryList::clear_cut_entries( bool F_visually_too )
{
    for( Entry* e : m_cut_entries )
    {
        e->set_status_flag( ES::CUT, false );
        m_cut_entries.insert( e );
        if( F_visually_too )
            refresh_row_label( e );
    }

    m_cut_entries.clear();
}

bool
WidgetEntryList::handle_drop( const Glib::ValueBase& value, double x, double y,
                              const Glib::RefPtr< Gtk::ListItem >& list_item )
{
    bool F_ret_val  { true };
    auto row        { std::dynamic_pointer_cast< Gtk::TreeListRow >( list_item->get_item() ) };
    if( !row ) return {};
    auto gentry     { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
    if( !gentry ) return {};
    Entry* p2entry_rcvn { gentry->m_p2entry };

    // text onto entry:
    if( G_VALUE_HOLDS( value.gobj(), Glib::Value< Ustring >::value_type() ) )
    {
        Glib::Value< Ustring > value_ustring;
        value_ustring.init( value.gobj() );

        if( m_drop_position == DropPosition::INTO )
        {
            p2entry_rcvn->insert_text( Ustring::npos, value_ustring.get(), ParaInhClass::NONE );
            refresh_row( p2entry_rcvn );
        }
        else
        {
            // p2entry_rcvn first used for style inheritance:
            Entry* entry_new { Diary::d->create_entry( p2entry_rcvn, true, Date::get_today(),
                                                       value_ustring.get(),
                                                       VT::ETS::INHERIT::I ) };
            // actual position is set in the next step:
            p2entry_rcvn->get_diary()->move_entry( entry_new, p2entry_rcvn, m_drop_position );
            AppWindow::p->UI_diary->update_entry_list();
        }
    }
    else if( G_VALUE_HOLDS( value.gobj(), Theme::GValue::value_type() ) )
    {
        Theme::GValue value_theme;
        value_theme.init( value.gobj() );
        const Theme* p2theme { value_theme.get() };

        p2entry_rcvn->set_theme( p2theme );
        AppWindow::p->refresh_theme_in_TVDs( p2theme );
    }
    else if( G_VALUE_HOLDS( value.gobj(), LoG::Entry::GValue::value_type() ) )
    {
        LoG::Entry::GValue  value_entry;
        value_entry.init( value.gobj() );
        LoG::Entry*         p2entry_drpd  { value_entry.get() };

        p2entry_rcvn->get_diary()->move_entry( p2entry_drpd, p2entry_rcvn, m_drop_position );

        // AppWindow::p->UI_entry->refresh_extra_info();
        AppWindow::p->UI_diary->update_entry_list();
    }
    else if( G_VALUE_HOLDS( value.gobj(), LoG::EntrySelection::GValue::value_type() ) )
    {
        // no need to go down the the following road as we already possess the selected entries
        // LoG::EntrySelection::GValue value_multi;
        // value_multi.init( value.gobj() );
        // LoG::EntrySelection*        p2entries   { value_multi.get() };

        p2entry_rcvn->get_diary()->move_entries( &m_selected_entries, p2entry_rcvn,
                                                 m_drop_position );

        // AppWindow::p->UI_entry->refresh_extra_info();
        AppWindow::p->UI_diary->update_entry_list();
    }
    else
    {
        F_ret_val = false;
    }

    m_drop_position = DropPosition::NONE;
    m_id_dragged_entry = DEID::UNSET;
    return F_ret_val;
}

void
WidgetEntryList::handle_selection_changed( guint pos, guint n_items )
{
    // NOTE: we did not use the pos & n_items as it turned out to be quite hard
    // to keep an uptodate list of selected items

    if( Lifeograph::is_internal_operations_ongoing() ) return;

    m_selected_entries.clear();

    if( m_p2expander_last )
    {
        m_p2expander_last->unset_name();
        m_p2expander_last = nullptr;
    }

    auto&& selected_entries { m_selection_model->get_selection() };
    for( auto it : *selected_entries )
    {
        auto row    { m_TLM->get_row( it ) };
        if( !row ) continue;
        auto gentry { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
        if( !gentry ) continue;

        m_selected_entries.insert( gentry->m_p2entry );
    }

    // show the entry if it is the only one selected:
    if( m_selected_entries.size() == 1 && !m_F_inhibit_show_entry )
    {
        Entry* entry { *m_selected_entries.begin() };
        AppWindow::p->UI_entry->show( entry );
        AppWindow::p->UI_extra->set_entry( entry );
    }
    m_F_inhibit_show_entry = false;
}

void
WidgetEntryList::snapshot_vfunc( const Glib::RefPtr< Gtk::Snapshot >& snapshot )
{
    const static std::vector< double > dash_pattern { 7.0, 7.0 };

    Gtk::ListView::snapshot_vfunc( snapshot );

    if( m_drop_position != DropPosition::NONE || m_id_dragged_entry != DEID::UNSET )
    {
        const int       w       { get_width() };
        const int       h       { get_height() };
        auto            cr      { snapshot->append_cairo( Gdk::Rectangle( 0, 0, w, h ) ) };

        if( m_drop_position != DropPosition::NONE )
        {
            Gdk::Cairo::set_source_rgba( cr, Gdk::RGBA( "#FF333366" ) );
            // cr->set_line_join( Cairo::Context::LineJoin::ROUND );

            cr->rectangle( m_drop_coord_x, m_drop_coord_y, w - m_drop_coord_x, m_drop_rect_h );

            cr->fill();
        }

        if( m_id_dragged_entry != DEID::UNSET ) // only true for entries
        {
            Gdk::Cairo::set_source_rgba( cr, Gdk::RGBA( "#CC3333AA" ) );
            cr->set_line_width( 2.0 );
            cr->set_dash( dash_pattern, 0.0 );
            cr->move_to( OFFSET_DROP_INTO, 0.0 );
            cr->rel_line_to( 0.0, h );
            cr->stroke();
        }
    }
}

void
WidgetEntryList::do_for_each_selected_entry( const std::function< void( Entry* ) >& slot )
{
    for( Entry* entry : m_selected_entries )
    {
        slot( entry );
    }
}

void
WidgetEntryList::handle_expand_changed( const Glib::RefPtr< Gtk::TreeListRow >& row )
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    auto gentry { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };
    if( gentry )
    {
        gentry->m_p2entry->set_expanded( row->get_expanded() );

        if( AppWindow::p->UI_entry->get_entry()->is_descendant_of( gentry->m_p2entry ) )
            Glib::signal_idle().connect_once(
                    [ this ]()
                    {
                        Entry* p2ec { AppWindow::p->UI_entry->get_entry() };
                        select( p2ec, p2ec->get_first_exposed_upwards() );
                    } );
    }

    PRINT_DEBUG( "entry [", gentry->m_p2entry->get_name(),
                 "] new expand state: ", row->get_expanded() );
}

void
WidgetEntryList::handle_row_activated( guint pos )
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    PRINT_DEBUG( "Activated entry no: ", pos );

    // auto row { m_TLM->get_row( pos ) };
    // if( !row ) return;
    // auto gentry { std::dynamic_pointer_cast< GObjectEntry >( row->get_item() ) };

    // if( gentry )
    if( m_selected_entries.size() == 1 )
    {
        Entry* entry { *m_selected_entries.begin() };

        if( !AppWindow::p->UI_entry->is_cur_entry( entry ) )
        {
            AppWindow::p->UI_entry->show( /*gentry->m_p2*/entry );
            AppWindow::p->UI_extra->set_entry( /*gentry->m_p2*/entry );
        }
    }
}

