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

    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 "../diaryelements/filter.hpp"
#include "../helpers_gtk.hpp"
#include "../python/py_bindings.hpp"
#include "widget_filterpicker.hpp"
#include "widget_filter.hpp"


using namespace LoG;

// FILTERER ========================================================================================

// FILTERER STATUS =================================================================================
FiltererStatusUI::FiltererStatusUI( WidgetFilter* ctr, FiltererStatus* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    auto L_text{ Gtk::manage( new Gtk::Label( _( "Whose status is one of" ) ) ) };

    auto Bx_status{ Gtk::manage( new Gtk::Box( Gtk::Orientation::HORIZONTAL, 0 ) ) };
    m_ChB_not_todo = Gtk::manage( new Gtk::ToggleButton );
    m_ChB_open = Gtk::manage( new Gtk::ToggleButton );
    m_ChB_progressed = Gtk::manage( new Gtk::ToggleButton );
    m_ChB_done = Gtk::manage( new Gtk::ToggleButton );
    m_ChB_canceled = Gtk::manage( new Gtk::ToggleButton );

    auto I_not_todo{ Gtk::manage( new Gtk::Image ) };
    auto I_open{ Gtk::manage( new Gtk::Image ) };
    auto I_progressed{ Gtk::manage( new Gtk::Image ) };
    auto I_done{ Gtk::manage( new Gtk::Image ) };
    auto I_canceled{ Gtk::manage( new Gtk::Image ) };

    I_not_todo->set_from_icon_name( "entry-16" );
    I_open->set_from_icon_name( "todo_open-16" );
    I_progressed->set_from_icon_name( "todo_progressed-16" );
    I_done->set_from_icon_name( "todo_done-16" );
    I_canceled->set_from_icon_name( "todo_canceled-16" );

    m_ChB_not_todo->set_child( *I_not_todo );
    m_ChB_open->set_child( *I_open );
    m_ChB_progressed->set_child( *I_progressed );
    m_ChB_done->set_child( *I_done );
    m_ChB_canceled->set_child( *I_canceled );

    Bx_status->add_css_class( "linked" );
    Bx_status->append( *m_ChB_not_todo );
    Bx_status->append( *m_ChB_open );
    Bx_status->append( *m_ChB_progressed );
    Bx_status->append( *m_ChB_done );
    Bx_status->append( *m_ChB_canceled );

    m_ChB_not_todo->set_active(   m_p2filterer->m_included_statuses & ES::SHOW_NOT_TODO );
    m_ChB_open->set_active(       m_p2filterer->m_included_statuses & ES::SHOW_TODO );
    m_ChB_progressed->set_active( m_p2filterer->m_included_statuses & ES::SHOW_PROGRESSED );
    m_ChB_done->set_active(       m_p2filterer->m_included_statuses & ES::SHOW_DONE );
    m_ChB_canceled->set_active(   m_p2filterer->m_included_statuses & ES::SHOW_CANCELED );

    m_ChB_not_todo->signal_toggled().connect( [ this ](){ update_state(); } );
    m_ChB_open->signal_toggled().connect( [ this ](){ update_state(); } );
    m_ChB_progressed->signal_toggled().connect( [ this ](){ update_state(); } );
    m_ChB_done->signal_toggled().connect( [ this ](){ update_state(); } );
    m_ChB_canceled->signal_toggled().connect( [ this ](){ update_state(); } );

    append( *L_text );
    append( *Bx_status );
}

void
FiltererStatusUI::update_state()
{
    m_p2filterer->m_included_statuses = 0;

    if( m_ChB_not_todo->get_active() )
        m_p2filterer->m_included_statuses |= ES::SHOW_NOT_TODO;
    if( m_ChB_open->get_active() )
        m_p2filterer->m_included_statuses |= ES::SHOW_TODO;
    if( m_ChB_progressed->get_active() )
        m_p2filterer->m_included_statuses |= ES::SHOW_PROGRESSED;
    if( m_ChB_done->get_active() )
        m_p2filterer->m_included_statuses |= ES::SHOW_DONE;
    if( m_ChB_canceled->get_active() )
        m_p2filterer->m_included_statuses |= ES::SHOW_CANCELED;

    m_p2WF->emit_changed();
}

// FILTERER SIZE ===================================================================================
FiltererSizeUI::FiltererSizeUI( WidgetFilter* ctr, FiltererSize* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    Gtk::Box*     Bx_range;
    Gtk::Button*  B_incl_b;
    Gtk::Button*  B_incl_e;
    auto&&        builder { Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/flt_range.ui" ) };

    m_DD_type   = Gtk::make_managed< Gtk::DropDown >( VT::strlistize_propspace< VT::SO >() );
    Bx_range    = builder->get_widget< Gtk::Box >( "Bx_range" );
    B_incl_b    = builder->get_widget< Gtk::Button >( "B_incl_b" );
    B_incl_e    = builder->get_widget< Gtk::Button >( "B_incl_e" );
    m_L_incl_b  = builder->get_widget< Gtk::Label >( "L_incl_b" );
    m_L_incl_e  = builder->get_widget< Gtk::Label >( "L_incl_e" );
    m_E_range_b = Gtk::Builder::get_widget_derived< EntryClear >( builder, "E_bgn" );
    m_E_range_e = Gtk::Builder::get_widget_derived< EntryClear >( builder, "E_end" );

    m_DD_type->set_selected( VT::get_v< VT::SO, unsigned, char >( m_p2filterer->m_type ) );

    m_DD_type->property_selected().signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_type = VT::get_v< VT::SO,
                                    char,
                                    unsigned >( m_DD_type->get_selected() );
                m_p2WF->emit_changed();
            } );

    m_E_range_b->set_placeholder_text( "--" );
    if( m_p2filterer->m_range_b != -1 )
        m_E_range_b->set_text( STR::format_number( m_p2filterer->m_range_b ) );
    m_E_range_b->signal_changed().connect(
            [ this ]()
            {
                if( Lifeograph::is_internal_operations_ongoing() ) return;
                const auto& text{ m_E_range_b->get_text() };
                m_p2filterer->m_range_b = text.empty() ? -1 : STR::get_d( text );
                m_p2WF->emit_changed();
            } );

    m_E_range_e->set_placeholder_text( "--" );
    if( m_p2filterer->m_range_e != -1 )
        m_E_range_e->set_text( STR::format_number( m_p2filterer->m_range_e ) );
    m_E_range_e->signal_changed().connect(
            [ this ]()
            {
                if( Lifeograph::is_internal_operations_ongoing() ) return;
                const auto& text { m_E_range_e->get_text() };
                m_p2filterer->m_range_e = text.empty() ? -1 : STR::get_d( text );
                m_p2WF->emit_changed();
            } );

    m_L_incl_b->set_label( m_p2filterer->m_F_incl_b ? "[" : "(" );
    B_incl_b->signal_clicked().connect(
            [ this ]()
            {
                m_p2filterer->m_F_incl_b = !m_p2filterer->m_F_incl_b;
                m_L_incl_b->set_label( m_p2filterer->m_F_incl_b ? "[" : "(" );
                m_p2WF->emit_changed();
            } );

    m_L_incl_e->set_label( m_p2filterer->m_F_incl_e ? "]" : ")" );
    B_incl_e->signal_clicked().connect(
            [ this ]()
            {
                m_p2filterer->m_F_incl_e = !m_p2filterer->m_F_incl_e;
                m_L_incl_e->set_label( m_p2filterer->m_F_incl_e ? "]" : ")" );
                m_p2WF->emit_changed();
            } );

    append( *m_DD_type );
    append( *Gtk::manage( new Gtk::Label( "∈" ) ) );
    append( *Bx_range );
}

// FILTERER FAVORITE ===============================================================================
FiltererFavoriteUI::FiltererFavoriteUI( WidgetFilter* ctr, FiltererFavorite* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    auto L_name{ Gtk::manage( new Gtk::Label( _( "Is favorite" ) ) ) };

    append( *L_name );
    //L_name->insert_before( *this, *m_TB_not ); maybe later
}

// FILTERER TRASHED ================================================================================
FiltererTrashedUI::FiltererTrashedUI( WidgetFilter* ctr, FiltererTrashed* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    auto L_name{ Gtk::manage( new Gtk::Label( _( "Is trashed" ) ) ) };

    append( *L_name );
}

// FILTERER UNIT ===================================================================================
FiltererUnitUI::FiltererUnitUI( WidgetFilter* ctr, FiltererUnit* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    auto L_name{ Gtk::manage( new Gtk::Label( _( "Has the unit" ) ) ) };
    m_E_unit =  Gtk::manage( new EntryClear );

    m_E_unit->set_text( m_p2filterer->m_unit );
    m_E_unit->signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_unit = m_E_unit->get_text();
                m_p2WF->emit_changed();
            } );

    append( *L_name );
    append( *m_E_unit );
}

// FILTERER IS NOT =================================================================================
FiltererIsUI::FiltererIsUI( WidgetFilter* ctr, FiltererIs* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    auto          L_name  { Gtk::manage( new Gtk::Label( _( "Is tag" ) ) ) };
    Diary* const  p2diary { m_p2filterer->m_p2container->m_p2diary };

    m_WTP       = Gtk::make_managed< WidgetTagPicker >( false );
    m_DD_depth  = Gtk::make_managed< Gtk::DropDown >( VT::strlistize_propspace< VT::OP_DEPTH >() );

    m_WTP->set_diary( p2diary );
    m_WTP->set_width_chars( 30 );
    m_WTP->set_elem( m_p2filterer->m_p2tag );
    m_WTP->set_entry_only( false );
    m_WTP->m_Sg_updated.connect(
            [ this ]( DiaryElemTag* t )
            { m_p2filterer->m_p2tag = t; m_p2WF->emit_changed(); } );

    m_DD_depth->set_selected(
            VT::get_v< VT::OP_DEPTH, unsigned, int >( m_p2filterer->m_depth ) );

    m_DD_depth->property_selected().signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_depth = VT::get_v< VT::OP_DEPTH,
                                                   int,
                                                   unsigned >( m_DD_depth->get_selected() );
                m_p2WF->emit_changed();
            } );

    append( *L_name );
    append( *m_WTP );
    append( *m_DD_depth );
}

// FILTERER TAGGED BY ==============================================================================
FiltererTaggedByUI::FiltererTaggedByUI( WidgetFilter* ctr, FiltererTaggedBy* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    auto L_name { Gtk::make_managed< Gtk::Label >( _( "Has the tag" ) ) };
    m_WTP = Gtk::make_managed< WidgetTagPicker >( false, "tag-16-symbolic" );
    m_ChB_consider_parents = Gtk::make_managed< Gtk::CheckButton >( _( "Consider Parents, Too" ) );

    m_WTP->set_diary( m_p2filterer->m_p2container->m_p2diary );
    m_WTP->set_elem( m_p2filterer->m_tag );
    m_WTP->m_Sg_updated.connect(
            [ this ]( DiaryElemTag* t )
            { m_p2filterer->m_tag = t; m_p2WF->emit_changed(); } );

    m_ChB_consider_parents->set_active( m_p2filterer->m_F_consider_parents );
    m_ChB_consider_parents->signal_toggled().connect(
            [ this ]()
            {
                m_p2filterer->m_F_consider_parents = m_ChB_consider_parents->get_active();
                m_p2WF->emit_changed();
            } );

    append( *L_name );
    append( *m_WTP );
    append( *m_ChB_consider_parents );
}

// FILTERER SUBTAGGED BY ===========================================================================
FiltererSubtaggedByUI::FiltererSubtaggedByUI( WidgetFilter* ctr, FiltererSubtaggedBy* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    Gtk::Box*   Bx_main;
    auto&&      builder { Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/flt_subtag.ui" ) };

    Bx_main     = builder->get_widget< Gtk::Box >( "Bx_main" );
    m_CB_type   = builder->get_widget< Gtk::ComboBoxText >( "CB_type" );
    m_DD_rel    = builder->get_widget< Gtk::DropDown >( "DD_rel" );
    m_WTP_tag_p = Gtk::Builder::get_widget_derived< WidgetTagPicker >( builder, "E_tag_parent" );
    m_WTP_tag_c = Gtk::Builder::get_widget_derived< WidgetTagPicker >( builder, "E_tag_child" );

    m_DD_rel->set_model( Gtk::StringList::create( { "<", "=", ">" } ) );
    m_DD_rel->set_selected( STR::get_pos_c( VT::AR_CHARS, m_p2filterer->m_relation, 1 ) );
    // 1 designates '=' as the default value
    m_DD_rel->property_selected().signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_relation = VT::AR_CHARS[ m_DD_rel->get_selected() ];
                m_p2WF->emit_changed();
            } );

    m_CB_type->set_active( m_p2filterer->m_type & VT::SEQ_FILTER );
    m_CB_type->signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_type = m_CB_type->get_active_row_number();
                m_p2WF->emit_changed();
            } );

    m_WTP_tag_p->set_diary( m_p2filterer->m_p2container->m_p2diary );
    m_WTP_tag_p->set_elem( m_p2filterer->m_tag_parent );
    m_WTP_tag_p->m_Sg_updated.connect(
            [ this ]( DiaryElemTag* t )
            { m_p2filterer->m_tag_parent = t; m_p2WF->emit_changed(); } );

    m_WTP_tag_c->set_diary( m_p2filterer->m_p2container->m_p2diary );
    m_WTP_tag_c->set_elem( m_p2filterer->m_tag_child );
    m_WTP_tag_c->m_Sg_updated.connect(
            [ this ]( DiaryElemTag* t )
            { m_p2filterer->m_tag_child = t; m_p2WF->emit_changed(); } );

    append( *Bx_main );
}

// FILTERER DEFINES TAG ============================================================================
FiltererDefinesTagUI::FiltererDefinesTagUI( WidgetFilter* ctr, FiltererDefinesTag* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    auto L_name{ Gtk::manage( new Gtk::Label( _( "Defines tag" ) ) ) };
    append( *L_name );
}

// FILTERER TAG VALUE ==============================================================================
FiltererTagValueUI::FiltererTagValueUI( WidgetFilter* ctr, FiltererTagValue* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    Gtk::Box*    Bx_range;
    Gtk::Button* B_incl_b;
    Gtk::Button* B_incl_e;
    auto&&       builder { Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/flt_range.ui" ) };

    m_WTP = Gtk::manage( new WidgetTagPicker( false, "tag-16-symbolic" ) );
    m_DD_type = Gtk::make_managed< Gtk::DropDown >( VT::strlistize_propspace< VT::TVTS >() );

    Bx_range    = builder->get_widget< Gtk::Box >( "Bx_range" );
    B_incl_b    = builder->get_widget< Gtk::Button >( "B_incl_b" );
    B_incl_e    = builder->get_widget< Gtk::Button >( "B_incl_e" );
    m_L_incl_b  = builder->get_widget< Gtk::Label >( "L_incl_b" );
    m_L_incl_e  = builder->get_widget< Gtk::Label >( "L_incl_e" );
    m_E_range_b = Gtk::Builder::get_widget_derived< EntryClear >( builder, "E_bgn" );
    m_E_range_e = Gtk::Builder::get_widget_derived< EntryClear >( builder, "E_end" );

    m_WTP->set_width_chars( 20 );
    m_WTP->set_diary( m_p2filterer->m_p2container->m_p2diary );
    m_WTP->set_elem( m_p2filterer->m_tag );
    m_WTP->m_Sg_updated.connect(
            [ this ]( DiaryElemTag* t )
            { m_p2filterer->m_tag = t; m_p2WF->emit_changed(); } );

    m_DD_type->set_selected( VT::get_v< VT::TVTS, unsigned, int >( m_p2filterer->m_type ) );

    m_DD_type->property_selected().signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_type = VT::get_v< VT::TVTS,
                                                  int,
                                                  unsigned >( m_DD_type->get_selected() );
                m_p2WF->emit_changed();
            } );

    m_E_range_b->set_placeholder_text( "-∞" );
    if( m_p2filterer->m_range_b != Constants::INFINITY_MNS )
        m_E_range_b->set_text( STR::format_number( m_p2filterer->m_range_b ) );
    m_E_range_b->signal_changed().connect(
            [ this ]()
            {
                if( Lifeograph::is_internal_operations_ongoing() ) return;
                const auto& text{ m_E_range_b->get_text() };
                m_p2filterer->m_range_b = text.empty() ? Constants::INFINITY_MNS
                                                       : STR::get_d( text );
                m_p2WF->emit_changed();
            } );

    m_E_range_e->set_placeholder_text( "+∞" );
    if( m_p2filterer->m_range_e != Constants::INFINITY_PLS )
        m_E_range_e->set_text( STR::format_number( m_p2filterer->m_range_e ) );
    m_E_range_e->signal_changed().connect(
            [ this ]()
            {
                if( Lifeograph::is_internal_operations_ongoing() ) return;
                const auto& text{ m_E_range_e->get_text() };
                m_p2filterer->m_range_e = text.empty() ? Constants::INFINITY_PLS
                                                       : STR::get_d( text );
                m_p2WF->emit_changed();
            } );

    m_L_incl_b->set_label( m_p2filterer->m_F_incl_b ? "[" : "(" );
    B_incl_b->signal_clicked().connect(
            [ this ]()
            {
                m_p2filterer->m_F_incl_b = !m_p2filterer->m_F_incl_b;
                m_L_incl_b->set_label( m_p2filterer->m_F_incl_b ? "[" : "(" );
                m_p2WF->emit_changed();
            } );

    m_L_incl_e->set_label( m_p2filterer->m_F_incl_e ? "]" : ")" );
    B_incl_e->signal_clicked().connect(
            [ this ]()
            {
                m_p2filterer->m_F_incl_e = !m_p2filterer->m_F_incl_e;
                m_L_incl_e->set_label( m_p2filterer->m_F_incl_e ? "]" : ")" );
                m_p2WF->emit_changed();
            } );

    append( *m_WTP );
    append( *m_DD_type );
    append( *Gtk::manage( new Gtk::Label( "∈" ) ) );
    append( *Bx_range );
}

// FILTERER THEME ==================================================================================
FiltererThemeUI::FiltererThemeUI( WidgetFilter* ctr, FiltererTheme* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    auto L_name { Gtk::manage( new Gtk::Label( _( "Has the theme" ) ) ) };
    m_W_theme_picker = Gtk::manage( new WidgetPicker< Theme > );

    m_W_theme_picker->set_map( m_p2filterer->m_p2container->m_p2diary->get_p2themes(),
                               nullptr,
                               &m_p2filterer->m_theme );
    m_W_theme_picker->set_select_only( true );
    if( m_p2filterer->m_theme )
        m_W_theme_picker->set_text( m_p2filterer->m_theme->get_name() );
    m_W_theme_picker->signal_active_changed().connect(
            [ this ]( Theme* p2theme )
            {
                m_p2filterer->m_theme = p2theme;
                m_p2WF->emit_changed();
            } );

    append( *L_name );
    append( *m_W_theme_picker );
}

// FILTERER BETWEEN DATES ==========================================================================
FiltererBetweenDatesUI::FiltererBetweenDatesUI( WidgetFilter* ctr,
                                                FiltererBetweenDates* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    Gtk::Box*    Bx_range;
    Gtk::Button* B_incl_b;
    Gtk::Button* B_incl_e;
    auto&&       builder { Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/flt_range.ui" ) };

    Bx_range    = builder->get_widget< Gtk::Box >( "Bx_range" );
    B_incl_b    = builder->get_widget< Gtk::Button >( "B_incl_b" );
    B_incl_e    = builder->get_widget< Gtk::Button >( "B_incl_e" );
    m_L_incl_b  = builder->get_widget< Gtk::Label >( "L_incl_b" );
    m_L_incl_e  = builder->get_widget< Gtk::Label >( "L_incl_e" );
    m_W_date_picker_b = Gtk::Builder::get_widget_derived< WidgetDatePicker >( builder, "E_bgn" );
    m_W_date_picker_e = Gtk::Builder::get_widget_derived< WidgetDatePicker >( builder, "E_end" );

    m_W_date_picker_b->set_width_chars( 13 );
    m_W_date_picker_b->set_date( m_p2filterer->m_date_b );
    m_W_date_picker_b->signal_date_set().connect(
            [ this ]( DateV d ){ m_p2filterer->m_date_b = d; m_p2WF->emit_changed(); } );

    m_W_date_picker_e->set_width_chars( 13 );
    m_W_date_picker_e->set_date( m_p2filterer->m_date_e );
    m_W_date_picker_e->signal_date_set().connect(
            [ this ]( DateV d ){ m_p2filterer->m_date_e = d; m_p2WF->emit_changed(); } );

    m_L_incl_b->set_label( m_p2filterer->m_F_incl_b ? "[" : "(" );
    B_incl_b->signal_clicked().connect(
            [ this ]()
            {
                m_p2filterer->m_F_incl_b = !m_p2filterer->m_F_incl_b;
                m_L_incl_b->set_label( m_p2filterer->m_F_incl_b ? "[" : "(" );
                m_p2WF->emit_changed();
            } );

    m_L_incl_e->set_label( m_p2filterer->m_F_incl_e ? "]" : ")" );
    B_incl_e->signal_clicked().connect(
            [ this ]()
            {
                m_p2filterer->m_F_incl_e = !m_p2filterer->m_F_incl_e;
                m_L_incl_e->set_label( m_p2filterer->m_F_incl_e ? "]" : ")" );
                m_p2WF->emit_changed();
            } );

    append( *Gtk::manage( new Gtk::Label( _( "Between" ) ) ) );
    append( *Bx_range );
}

// FILTERER BETWEEN DATE OFFSETS ===================================================================
FiltererBetweenDateOffsetsUI::FiltererBetweenDateOffsetsUI( WidgetFilter* ctr,
                                                            FiltererBetweenDateOffsets* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    auto&& builder    { Lifeograph::create_gui( Lifeograph::SHAREDIR +
                                                "/ui/flt_btw_date_offsets.ui" ) };
    Gtk::Box* Bx_main { builder->get_widget< Gtk::Box >( "Bx_main" ) };
    m_DD_date_type    = builder->get_widget< Gtk::DropDown >( "DD_date" );
    m_SB_offset_bgn   = builder->get_widget< Gtk::SpinButton >( "SB_offset_bgn" );
    m_SB_offset_end   = builder->get_widget< Gtk::SpinButton >( "SB_offset_end" );

    m_DD_date_type->set_model( VT::strlistize_propspace< VT::DT >() );
    m_DD_date_type->set_selected(
            VT::get_v< VT::DT, unsigned, char >( m_p2filterer->m_date_type ) );
    m_DD_date_type->property_selected().signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_date_type =
                        VT::get_v< VT::DT, char, unsigned >( m_DD_date_type->get_selected() );
                m_p2WF->emit_changed();
            } );
    auto adj_bgn = Gtk::Adjustment::create( 0.0, -1000.0, 1000.0, 1.0, 10.0, 0.0 );
    auto adj_end = Gtk::Adjustment::create( 0.0, -1000.0, 1000.0, 1.0, 10.0, 0.0 );

    m_SB_offset_bgn->set_adjustment( adj_bgn );
    m_SB_offset_bgn->set_value( p2filterer->m_offset_bgn );
    m_SB_offset_bgn->signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_offset_bgn = m_SB_offset_bgn->get_value_as_int();
                m_p2WF->emit_changed();
            } );

    m_SB_offset_end->set_adjustment( adj_end );
    m_SB_offset_end->set_value( p2filterer->m_offset_end );
    m_SB_offset_end->signal_value_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_offset_end = m_SB_offset_end->get_value_as_int();
                m_p2WF->emit_changed();
            } );

    append( *Bx_main );
}

// FILTERER BETWEEN ENTRIES ========================================================================
FiltererBetweenEntriesUI::FiltererBetweenEntriesUI( WidgetFilter* ctr,
                                                    FiltererBetweenEntries* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    Gtk::Box*    Bx_range;
    Gtk::Button* B_incl_b;
    Gtk::Button* B_incl_e;
    auto&&       builder { Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/flt_range.ui" ) };

    Bx_range    = builder->get_widget< Gtk::Box >( "Bx_range" );
    B_incl_b    = builder->get_widget< Gtk::Button >( "B_incl_b" );
    B_incl_e    = builder->get_widget< Gtk::Button >( "B_incl_e" );
    m_L_incl_b  = builder->get_widget< Gtk::Label >( "L_incl_b" );
    m_L_incl_e  = builder->get_widget< Gtk::Label >( "L_incl_e" );
    m_WTP_bgn   = Gtk::Builder::get_widget_derived< WidgetTagPicker >( builder, "E_bgn" );
    m_WTP_end   = Gtk::Builder::get_widget_derived< WidgetTagPicker >( builder, "E_end" );

    m_WTP_bgn->set_width_chars( 20 );
    m_WTP_bgn->set_diary( m_p2filterer->m_p2container->m_p2diary );
    m_WTP_bgn->set_elem( m_p2filterer->m_entry_b );
    m_WTP_bgn->set_entry_only( true );
    m_WTP_bgn->m_Sg_updated_entry.connect(
            [ this ]( Entry* e ){ m_p2filterer->m_entry_b = e; m_p2WF->emit_changed(); } );

    m_WTP_end->set_width_chars( 20 );
    m_WTP_end->set_diary( m_p2filterer->m_p2container->m_p2diary );
    m_WTP_end->set_elem( m_p2filterer->m_entry_e );
    m_WTP_end->set_entry_only( true );
    m_WTP_end->m_Sg_updated_entry.connect(
            [ this ]( Entry* e ){ m_p2filterer->m_entry_e = e; m_p2WF->emit_changed(); } );

    m_L_incl_b->set_label( m_p2filterer->m_F_incl_b ? "[" : "(" );
    B_incl_b->signal_clicked().connect(
            [ this ]()
            {
                m_p2filterer->m_F_incl_b = !m_p2filterer->m_F_incl_b;
                m_L_incl_b->set_label( m_p2filterer->m_F_incl_b ? "[" : "(" );
                m_p2WF->emit_changed();
            } );

    m_L_incl_e->set_label( m_p2filterer->m_F_incl_e ? "]" : ")" );
    B_incl_e->signal_clicked().connect(
            [ this ]()
            {
                m_p2filterer->m_F_incl_e = !m_p2filterer->m_F_incl_e;
                m_L_incl_e->set_label( m_p2filterer->m_F_incl_e ? "]" : ")" );
                m_p2WF->emit_changed();
            } );

    append( *Gtk::manage( new Gtk::Label( _( "Between" ) ) ) );
    append( *Bx_range );
}

// FILTERER COMPLETION =============================================================================
FiltererCompletionUI::FiltererCompletionUI( WidgetFilter* ctr, FiltererCompletion* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    m_Sc_bgn = Gtk::manage( new Gtk::Scale( Gtk::Orientation::HORIZONTAL ) );
    m_Sc_end = Gtk::manage( new Gtk::Scale( Gtk::Orientation::HORIZONTAL ) );

    m_Sc_bgn->set_has_origin( false );
    m_Sc_bgn->set_value_pos( Gtk::PositionType::RIGHT );
    m_Sc_bgn->set_adjustment( Gtk::Adjustment::create( m_p2filterer->m_compl_b, 0.0, 100.0 ) );
    m_Sc_bgn->set_size_request( 160, -1 );
    m_Sc_bgn->signal_value_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_compl_b = m_Sc_bgn->get_value();
                m_p2WF->emit_changed();
            } );

    m_Sc_end->set_has_origin( false );
    m_Sc_end->set_value_pos( Gtk::PositionType::LEFT );
    m_Sc_end->set_adjustment( Gtk::Adjustment::create( m_p2filterer->m_compl_e, 0.0, 100.0 ) );
    m_Sc_end->set_size_request( 160, -1 );
    m_Sc_end->signal_value_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_compl_e = m_Sc_end->get_value();
                m_p2WF->emit_changed();
            } );

    append( *Gtk::manage( new Gtk::Label( _( "Completion is in range" ) ) ) );
    append( *m_Sc_bgn );
    append( *m_Sc_end );
}

// FILTERER CONTAINS TEXT ==========================================================================
FiltererContainsTextUI::FiltererContainsTextUI( WidgetFilter* ctr,
                                                FiltererContainsText* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    Gtk::Box*    Bx_main;
    auto&&       builder { Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/flt_text.ui" ) };

    Bx_main         = builder->get_widget< Gtk::Box >( "Bx_main" );
    m_E_text        = Gtk::Builder::get_widget_derived< EntryClear >( builder, "E_text" );
    m_TB_match_case = builder->get_widget< Gtk::ToggleButton >( "TB_match_case" );
    m_TB_use_regex  = builder->get_widget< Gtk::ToggleButton >( "TB_use_regex" );
    m_CB_extent     = builder->get_widget< Gtk::ComboBoxText >( "CB_extent" );

    m_E_text->set_text( m_p2filterer->m_text );
    m_TB_match_case->set_active( m_p2filterer->m_case_sensitive );
    m_TB_use_regex->set_active( m_p2filterer->m_use_regex );
    m_CB_extent->set_active( m_p2filterer->m_name_only ? 1 : 0 );

    m_E_text->signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_text = m_E_text->get_text();
                m_p2WF->emit_changed();
            } );

    m_TB_match_case->signal_toggled().connect(
            [ this ]()
            {
                m_p2filterer->m_case_sensitive = m_TB_match_case->get_active();
                m_p2WF->emit_changed();
            });
    m_TB_use_regex->signal_toggled().connect(
            [ this ]()
            {
                m_p2filterer->m_use_regex = m_TB_use_regex->get_active();
                m_p2WF->emit_changed();
            });

    m_CB_extent->signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_name_only = ( m_CB_extent->get_active_row_number() == 1 );
                m_p2WF->emit_changed();
            } );

    append( *Bx_main );
}

// FILTERER CHILD FILTER ===========================================================================
FiltererChildFilterUI::FiltererChildFilterUI( WidgetFilter* ctr, FiltererChildFilter* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    m_WFP_filter = Gtk::manage( new WidgetFilterPicker );

    m_WFP_filter->set_halign( Gtk::Align::START );

    m_WFP_filter->set_diary( m_p2filterer->m_p2container->m_p2diary );
    m_WFP_filter->set_active( m_p2filterer->m_p2filter );

    m_WFP_filter->set_obj_classes( FOC::PARAGRAPHS );
    m_WFP_filter->Sg_changed.connect(
            [ this ]( const Filter* filter )
            {
                m_p2filterer->m_p2filter = const_cast< Filter* >( filter );
                if( filter )
                    m_p2filterer->m_FC_stack = filter->get_filterer_stack();
                m_p2WF->emit_changed();
            } );

    append( *Gtk::manage( new Gtk::Label( _( "Satisfies filter" ) ) ) );
    append( *m_WFP_filter );
}

// FILTERER IS IMAGE ===============================================================================
FiltererHasImageUI::FiltererHasImageUI( WidgetFilter* ctr, FiltererHasImage* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    append( *Gtk::manage( new Gtk::Label( _( "Has image" ) ) ) );
}

// FILTERER HAS COORD ==============================================================================
FiltererHasCoordsUI::FiltererHasCoordsUI( WidgetFilter* ctr, FiltererHasCoords* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    append( *Gtk::manage( new Gtk::Label( _( "Has map coordinates" ) ) ) );
}

// FILTERER TITLE STYLE ============================================================================
FiltererTitleStyleUI::FiltererTitleStyleUI( WidgetFilter* ctr, FiltererTitleStyle* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    m_DD_title_style = Gtk::make_managed< Gtk::DropDown >( Lifeograph::s_entry_name_styles );
    m_DD_title_style->set_selected( VT::get_v< VT::ETS, unsigned, char >(
                                                m_p2filterer->m_title_style ) );

    append( *Gtk::make_managed< Gtk::Label >( _( "Title style is" ) ) );
    append( *m_DD_title_style );

    m_DD_title_style->property_selected().signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_title_style = VT::get_v< VT::ETS, char, unsigned >(
                                                        m_DD_title_style ->get_selected() );
                m_p2WF->emit_changed();
            } );
}

// FILTERER HEADING LEVEL ==========================================================================
FiltererHeadingLevelUI::FiltererHeadingLevelUI( WidgetFilter* ctr,
                                                FiltererHeadingLevel* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    auto Bx_headings { Gtk::make_managed< Gtk::Box >() };
    m_TB_HN = Gtk::make_managed< Gtk::ToggleButton >( "H0" );
    m_TB_HM = Gtk::make_managed< Gtk::ToggleButton >( "H1" );
    m_TB_HL = Gtk::make_managed< Gtk::ToggleButton >( "H2" );
    m_TB_HT = Gtk::make_managed< Gtk::ToggleButton >( "H3" );

    Bx_headings->add_css_class( "linked" );
    Bx_headings->append( *m_TB_HN );
    Bx_headings->append( *m_TB_HM );
    Bx_headings->append( *m_TB_HL );
    Bx_headings->append( *m_TB_HT );

    // H levels are inverse so the entry title is 0
    // this is to make it easy to add furher sub heading levels int the future
    m_TB_HN->set_active( m_p2filterer->accepts ( VT::PHS::NORMAL::C ) );
    m_TB_HM->set_active( m_p2filterer->accepts ( VT::PHS::MEDIUM::C ) );
    m_TB_HL->set_active( m_p2filterer->accepts ( VT::PHS::LARGE::C ) );
    m_TB_HT->set_active( m_p2filterer->accepts ( VT::PHS::TITLE::C ) );

    append( *Gtk::make_managed< Gtk::Label >( _( "Heading level is" ) ) );
    append( *Bx_headings );

    m_TB_HN->signal_toggled().connect( [ this ](){ handle_change(); } );
    m_TB_HM->signal_toggled().connect( [ this ](){ handle_change(); } );
    m_TB_HL->signal_toggled().connect( [ this ](){ handle_change(); } );
    m_TB_HT->signal_toggled().connect( [ this ](){ handle_change(); } );
}

void
FiltererHeadingLevelUI::handle_change()
{
    m_p2filterer->m_H_levels = "";

    if( m_TB_HN->get_active() ) m_p2filterer->m_H_levels += VT::PHS::NORMAL::C;
    if( m_TB_HM->get_active() ) m_p2filterer->m_H_levels += VT::PHS::MEDIUM::C;
    if( m_TB_HL->get_active() ) m_p2filterer->m_H_levels += VT::PHS::LARGE::C;
    if( m_TB_HT->get_active() ) m_p2filterer->m_H_levels += VT::PHS::TITLE::C;

    m_p2WF->emit_changed();
}

// FILTERER HAS FORMAT =============================================================================
FiltererHasFormatUI::FiltererHasFormatUI( WidgetFilter* ctr, FiltererHasFormat* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    m_DD_format = Gtk::make_managed< DropDownAdvanced< int > >(
            VT::pairize_propspace< VT::FMT >() );
    m_DD_format->set_enable_search( true );

    m_DD_format->select_by_id( m_p2filterer->m_format );
    m_DD_format->Sg_selection_changed.connect(
            [ this ]( int sel )
            {
                m_p2filterer->m_format = sel;
                m_p2WF->emit_changed();
            } );

    append( *Gtk::make_managed< Gtk::Label >( _( "Has format" ) ) );
    append( *m_DD_format );
}

// FILTERER LIST TYPE ==============================================================================
FiltererListTypeUI::FiltererListTypeUI( WidgetFilter* ctr, FiltererListType* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    m_DD_type = Gtk::make_managed< Gtk::DropDown >( VT::strlistize_propspace< VT::PLS >() );

    m_DD_type->set_selected( VT::get_v< VT::PLS, unsigned, int >( m_p2filterer->m_type ) );
    m_DD_type->property_selected().signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_type = VT::get_v< VT::PLS,
                                                  int,
                                                  unsigned >( m_DD_type->get_selected() );
                m_p2WF->emit_changed();
            } );

    append( *Gtk::make_managed< Gtk::Label >( _( "List type" ) ) );
    append( *m_DD_type );
}

// FILTERER INDENTATION LEVEL ======================================================================
FiltererIndentationLevelUI::FiltererIndentationLevelUI( WidgetFilter* ctr,
                                                        FiltererIndentationLevel* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    m_DD_relation   = Gtk::make_managed< Gtk::DropDown >( STR::make_substr_vector( VT::AR_CHARS ) );
    m_E_indentation = Gtk::make_managed< Gtk::Entry >();

    m_DD_relation->set_show_arrow( false );
    m_DD_relation->set_selected( STR::get_pos_c( VT::AR_CHARS, m_p2filterer->m_relation, 1 ) );
    // 1 designates '=' as the default value
    m_DD_relation->property_selected().signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_relation = VT::AR_CHARS[ m_DD_relation->get_selected() ];
                m_p2WF->emit_changed();
            } );

    m_E_indentation->set_text( std::to_string( m_p2filterer->m_indentation ) );
    m_E_indentation->signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_indentation = std::stoi( m_E_indentation->get_text() );
                m_p2WF->emit_changed();
            } );

    append( *m_DD_relation );
    append( *m_E_indentation );
}

// FILTERER SCRIPT =================================================================================
FiltererScriptUI::FiltererScriptUI( WidgetFilter* ctr, FiltererScript* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    auto&&  builder { Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/flt_script.ui" ) };

    auto    Bx_main = builder->get_widget< Gtk::Box >( "Bx_main" );
    // auto    B_edit  = builder->get_widget< Gtk::Button >( "B_edit" ); TODO: 3.2
    m_DD_script     = Gtk::Builder::get_widget_derived< DropDownAdvanced< Ustring > >(
                                builder, "DD_script" );
    m_DD_script->clear();
    m_DD_script->add( STR::compose( "<", get_sstr_i( CSTR::NONE ), ">" ), "" );

    VecUstrings scripts;
    PyBindings::update_scripts_in_entries( true ); // update dirty only
    PyBindings::get_scripts( ScriptCategory::FILTER, scripts );

    for( auto script : scripts )
        m_DD_script->add( script, script );

    m_DD_script->select_by_id( m_p2filterer->m_script );

    m_DD_script->Sg_selection_changed.connect(
            [ this ]( String id )
            {
                m_p2filterer->m_script = id;
                m_p2WF->emit_changed();
            } );

    append( *Bx_main );
}

// FILTERER EQUALS =================================================================================
FiltererEqualsUI::FiltererEqualsUI( WidgetFilter* ctr, FiltererEquals* p2filterer )
: FiltererUI( ctr, p2filterer )
{
    m_DD_relation = Gtk::make_managed< Gtk::DropDown >(
            STR::make_substr_vector( VT::AR_CHARS ) );
    m_E_value     = Gtk::make_managed< Gtk::Entry >();

    m_DD_relation->set_show_arrow( false );
    m_DD_relation->set_selected( STR::get_pos_c( VT::AR_CHARS, m_p2filterer->m_relation, 1 ) );
    // 1 designates '=' as the default value
    m_DD_relation->property_selected().signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_relation = VT::AR_CHARS[ m_DD_relation->get_selected() ];
                m_p2WF->emit_changed();
            } );

    m_E_value->set_text( STR::format_number( m_p2filterer->m_value ) );
    m_E_value->signal_changed().connect(
            [ this ]()
            {
                m_p2filterer->m_value = STR::get_d( m_E_value->get_text() );
                m_p2WF->emit_changed();
            } );

    append( *m_DD_relation );
    append( *m_E_value );
}


// FILTERER CONTAINER ==============================================================================
FiltererContainerUI::FiltererContainerUI( WidgetFilter* p2WF, FiltererContainer* p2filterer )
:   FiltererUI( p2WF, p2filterer )
{
    set_valign( Gtk::Align::START );

    // HEADER
    m_Bx_header = Gtk::manage( new Gtk::Box( Gtk::Orientation::VERTICAL, 2 ) );
    m_L_logic = Gtk::manage( new Gtk::Label );
    auto Sp_top{ Gtk::manage( new Gtk::Separator( Gtk::Orientation::VERTICAL ) ) };
    auto Sp_btm{ Gtk::manage( new Gtk::Separator( Gtk::Orientation::VERTICAL ) ) };

    Sp_top->set_halign( Gtk::Align::CENTER );
    Sp_btm->set_halign( Gtk::Align::CENTER );
    Sp_top->set_vexpand( true );
    Sp_btm->set_vexpand( true );
    update_logic_label();

    m_Bx_header->append( *Sp_top );
    if( m_p2filterer->m_p2container ) // if it is a subgroup
        m_Bx_header->append( *m_L_logic );
    else
    {
        auto B_logic{ Gtk::manage( new Gtk::Button ) };
        B_logic->set_has_frame( false );
        B_logic->set_child( *m_L_logic );
        B_logic->signal_clicked().connect( [ this ](){ m_p2WF->toggle_logic(); } );
        m_Bx_header->append( *B_logic );
    }
    m_Bx_header->append( *Sp_btm );

    // CONTENTS
    m_Bx_contents   = Gtk::manage( new Gtk::Box( Gtk::Orientation::VERTICAL, 5 ) );
    m_WS_add        = Gtk::make_managed< WidgetSelector >();

    m_WS_add->set_icon_name( "list-add-symbolic" );
    m_WS_add->set_halign( Gtk::Align::START );
    m_Bx_contents->append( *m_WS_add );

    append( *m_Bx_header );
    append( *m_Bx_contents );

    set_from_stack();
}

void
FiltererContainerUI::clear_pipeline()
{
    remove_all_children_from_Bx( m_Bx_contents );
    m_p2filterer->clear_pipeline();
}

void
FiltererContainerUI::update_logic_label()
{
    if( m_p2filterer->m_F_or )
        m_L_logic->set_markup( STR::compose( "<b>", _( "OR" ), "</b>" ) );
    else
        m_L_logic->set_markup( STR::compose( "<b>", _( "AND" ), "</b>" ) );
}

void
FiltererContainerUI::set_from_stack()
{
    for( auto filterer : m_p2filterer->m_pipeline )
        filterer->initialize_ui( this );
}

void
FiltererContainerUI::remove_filterer( FiltererUI* filterer )
{
    m_p2filterer->remove_filterer( filterer->m_p2filterer );
    m_Bx_contents->remove( * dynamic_cast< Gtk::Box* >( filterer ) );

    FiltererContainerUI* filtererui { dynamic_cast< FiltererContainerUI* >( filterer ) };
    if( filtererui )
        m_subgroups.erase( filtererui );

    m_p2WF->emit_changed();
}

void
FiltererContainerUI::update_add_menu()
{
    // clear
    m_WS_add->clear_list();

    add_filterer_to_menu< FiltererTrashedUI, FiltererTrashed >();
    add_filterer_to_menu< FiltererStatusUI, FiltererStatus >();
    add_filterer_to_menu< FiltererSizeUI, FiltererSize >();
    add_filterer_to_menu< FiltererFavoriteUI, FiltererFavorite >();
    add_filterer_to_menu< FiltererUnitUI, FiltererUnit >();
    add_filterer_to_menu< FiltererIsUI, FiltererIs >();
    add_filterer_to_menu< FiltererBetweenDatesUI, FiltererBetweenDates >();
    add_filterer_to_menu< FiltererBetweenDateOffsetsUI, FiltererBetweenDateOffsets >();
    add_filterer_to_menu< FiltererBetweenEntriesUI, FiltererBetweenEntries >();
    add_filterer_to_menu< FiltererCompletionUI, FiltererCompletion >();
    add_filterer_to_menu< FiltererTaggedByUI, FiltererTaggedBy >();
    add_filterer_to_menu< FiltererTagValueUI, FiltererTagValue >();
    add_filterer_to_menu< FiltererSubtaggedByUI, FiltererSubtaggedBy >();
    add_filterer_to_menu< FiltererDefinesTagUI, FiltererDefinesTag >();
    add_filterer_to_menu< FiltererThemeUI, FiltererTheme >();
    add_filterer_to_menu< FiltererContainsTextUI, FiltererContainsText >();
    add_filterer_to_menu< FiltererHasImageUI, FiltererHasImage >();
    add_filterer_to_menu< FiltererHasCoordsUI, FiltererHasCoords >();
    add_filterer_to_menu< FiltererTitleStyleUI, FiltererTitleStyle >();
    add_filterer_to_menu< FiltererHeadingLevelUI, FiltererHeadingLevel >();
    add_filterer_to_menu< FiltererHasFormatUI, FiltererHasFormat >();
    add_filterer_to_menu< FiltererListTypeUI, FiltererListType >();
    add_filterer_to_menu< FiltererIndentationLevelUI, FiltererIndentationLevel >();
    add_filterer_to_menu< FiltererEqualsUI, FiltererEquals >();
    add_filterer_to_menu< FiltererScriptUI, FiltererScript >();
    // TODO: 3.2: check for cyclic references:
    add_filterer_to_menu< FiltererChildFilterUI, FiltererChildFilter >();
    if( m_p2filterer->m_p2container == nullptr ) // top level
        add_filterer_to_menu< FiltererContainerUI, FiltererContainer >();
}

// WIDGET FILTER ===================================================================================
void
WidgetFilter::set_enabled( bool flag_enabled )
{
    m_Bx_contents->set_sensitive( flag_enabled );
}

void
WidgetFilter::toggle_logic()
{
    m_p2filterer->toggle_logic();
    update_logic_label();

    for( auto sub : m_subgroups )
       sub->update_logic_label();

    emit_changed();
}

void
WidgetFilter::emit_changed()
{
    if( m_F_emit_signals )
        m_Sg_changed.emit();
}
