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

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

    This file is part of Lifeograph.

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

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

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

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


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iomanip>

#include "lifeograph.hpp"
#include "app_window.hpp"
#include "diary.hpp"
#include "views.hpp"
#include "panel_extra.hpp"


using namespace LIFEO;

// ELEMENT VIEW WITH CHART =========================================================================
template< class T >
ElementViewWithChart< T >::ElementViewWithChart( const Ustring type )
{
    try
    {
        auto builder{ Lifeograph::get_builder() };
        builder->get_widget_derived( "DA_" + type + "_chart", m_widgetchart );
        builder->get_widget( "Sc_" + type + "_chart", m_Sc_chart );
        builder->get_widget( "MB_" + type + "_chart", m_MB_chart );
        builder->get_widget( "Po_" + type + "_chart", m_Po_chart );
        builder->get_widget( "RB_" + type + "_chart_monthly", m_RB_chart_monthly );
        builder->get_widget( "RB_" + type + "_chart_yearly", m_RB_chart_yearly );
        builder->get_widget( "B_" + type + "_chart_underlay", m_B_underlay );
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create the chart" );
    }

    m_A_underlay = Lifeograph::p->add_action_bool( "underlay_prev_year_" + type,
            sigc::mem_fun( *this, &ElementViewWithChart::handle_chart_underlay ), false);
    m_Sc_chart->signal_change_value().connect(
            sigc::mem_fun( this, &ElementViewWithChart::handle_zoom ) );

    m_RB_chart_monthly->signal_toggled().connect(
            sigc::mem_fun( this, &ElementViewWithChart::handle_type_changed ) );
    m_RB_chart_yearly->signal_toggled().connect(
            sigc::mem_fun( this, &ElementViewWithChart::handle_type_changed ) );
}

template< class T >
bool
ElementViewWithChart< T >::handle_zoom( Gtk::ScrollType, double value )
{
    if( ! Lifeograph::s_internaloperation )
    {
        m_widgetchart->set_zoom( value > 1.0f ? 1.0f : ( value < 0.0f ) ? 0.0f : value );
    }
    return false;
}

template< class T >
void
ElementViewWithChart< T >::handle_type_changed()
{
    if( Lifeograph::s_internaloperation )
        return;

    if( m_RB_chart_monthly->get_active() )
    {
        ElementView< T >::m_ptr2elem->set_chart_type( ChartPoints::MONTHLY );
        m_B_underlay->set_visible( true );
    }
    else //if( m_RB_chart_yearly->get_active() )
    {
        ElementView< T >::m_ptr2elem->set_chart_type( ChartPoints::YEARLY );
        m_B_underlay->set_visible( false );
    }

    update_points();
}

template< class T >
void
ElementViewWithChart< T >::update_points( bool flag_reset_zoom )
{
    Lifeograph::s_internaloperation++;

    m_widgetchart->set_points( ElementView< T >::m_ptr2elem->create_chart_data(),
                               flag_reset_zoom ? 1.0f : -1.0f );
    if( flag_reset_zoom )
    {
        m_Sc_chart->set_value( 1.0f );
        m_Sc_chart->set_visible( m_widgetchart->is_zoom_possible() );
    }

    Lifeograph::s_internaloperation--;
}

template< class T >
void
ElementViewWithChart< T >::handle_chart_shown()
{
    switch( ElementView< T >::m_ptr2elem->get_chart_type() & ChartPoints::PERIOD_MASK )
    {
        case ChartPoints::MONTHLY:
            m_RB_chart_monthly->set_active();
            m_B_underlay->set_visible( true );
            break;
        case ChartPoints::YEARLY:
            m_RB_chart_yearly->set_active();
            m_B_underlay->set_visible( false );
            break;
    }

    bool flag_underlay{ ElementView< T >::m_ptr2elem->get_chart_type() &
                        ChartPoints::UNDERLAY_PREV_YEAR };

    m_A_underlay->change_state( flag_underlay );

    update_points();
}

template< class T >
void
ElementViewWithChart< T >::handle_chart_underlay()
{
    bool flag_underlay;
    m_A_underlay->get_state( flag_underlay );

    // The action's state does not change automatically:
    flag_underlay = !flag_underlay;
    m_A_underlay->change_state( flag_underlay );

    if( flag_underlay )
        ElementView< T >::m_ptr2elem->set_chart_type( ChartPoints::UNDERLAY_PREV_YEAR );
    else
        ElementView< T >::m_ptr2elem->set_chart_type( -ChartPoints::UNDERLAY_PREV_YEAR );

    update_points();
}

// TAG =============================================================================================
TagView::TagView()
:   ElementViewWithChart( "tag" )
{
    try
    {
        auto builder{ Lifeograph::get_builder() };
        builder->get_widget( "Bx_tag", m_box );
        builder->get_widget( "Po_tag", m_Po_elem );
        builder->get_widget( "B_tag_toggle_filtering", m_B_filtering );
        builder->get_widget( "B_tag_dismiss", m_B_dismiss );
        builder->get_widget( "G_tag_general", m_G_general );
        builder->get_widget( "G_tag_theme", m_G_theme );
        builder->get_widget( "CB_tag_category", m_CB_category );
        builder->get_widget( "CB_tag_type", m_CB_type );
        builder->get_widget( "CB_tag_unit", m_CB_unit );
        builder->get_widget( "E_tag_unit", m_E_unit );

        builder->get_widget( "Po_tag_theme", m_Po_theme );
        builder->get_widget( "TV_tag_theme", m_textview_theme );
        builder->get_widget( "B_tag_themes", m_B_themes );
        builder->get_widget( "B_tag_theme_reset", m_B_theme_reset );
        builder->get_widget( "MB_tag_theme", m_MB_theme );
        builder->get_widget( "Bx_tag_themes", m_Bx_themes );

        m_liststore_categories = Gtk::ListStore::create( colrec_category );
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create the tag view" );
    }

    Tag::shower = this;

    m_CB_category->set_model( m_liststore_categories );
    m_CB_category->pack_start( colrec_category.name );
    m_CB_category->set_row_separator_func(
            sigc::mem_fun( this, &TagView::is_row_separator ) );

    // THEME TEXT TAGS
    Glib::RefPtr< Gtk::TextBuffer::TagTable > tag_table =
            m_textview_theme->get_buffer()->get_tag_table();

    m_tag_action = Gtk::TextTag::create();
    tag_table->add( m_tag_action );

    m_tag_heading = Gtk::TextTag::create();
    m_tag_heading->property_weight() = Pango::WEIGHT_BOLD;
    m_tag_heading->property_scale() = 1.5;
    tag_table->add( m_tag_heading );

    m_tag_subheading = Gtk::TextTag::create();
    m_tag_subheading->property_weight() = Pango::WEIGHT_BOLD;
    m_tag_subheading->property_scale() = 1.2;
    tag_table->add( m_tag_subheading );

    m_tag_normal = Gtk::TextTag::create();
    m_tag_normal->property_underline() = Pango::UNDERLINE_ERROR;
    tag_table->add( m_tag_normal );

    m_tag_background = Gtk::TextTag::create();
    m_tag_background->property_weight() = Pango::WEIGHT_BOLD;
    m_tag_background->property_justification() = Gtk::JUSTIFY_RIGHT;
    tag_table->add( m_tag_background );

    m_tag_match = Gtk::TextTag::create();
    tag_table->add( m_tag_match );

    m_tag_highlight = Gtk::TextTag::create();
    tag_table->add( m_tag_highlight );

    m_tag_comment = Gtk::TextTag::create();
    m_tag_comment->property_scale() = 0.8;
    m_tag_comment->property_rise() = 5000;
    tag_table->add( m_tag_comment );

    m_tag_link = Gtk::TextTag::create();
    m_tag_link->property_underline() = Pango::UNDERLINE_SINGLE;
    tag_table->add( m_tag_link );

    m_tag_link_broken = Gtk::TextTag::create();
    m_tag_link_broken->property_underline() = Pango::UNDERLINE_SINGLE;
    tag_table->add( m_tag_link_broken );

    m_tag_region = Gtk::TextTag::create();
    tag_table->add( m_tag_region );

    m_tag_font = Gtk::TextTag::create();
    tag_table->add( m_tag_font );

    m_textview_theme->signal_motion_notify_event().connect_notify(
            sigc::mem_fun( this, &TagView::handle_theme_motion_notify_event ) );
    m_textview_theme->signal_button_release_event().connect_notify(
            sigc::mem_fun( this, &TagView::handle_theme_button_release_event ) );

    // SIGNALS
    m_B_dismiss->signal_clicked().connect(
            sigc::mem_fun( this, &TagView::dismiss_tag ) );

    m_CB_category->signal_changed().connect(
            sigc::mem_fun( this, &TagView::handle_category_changed ) );

    m_CB_type->signal_changed().connect(
            sigc::mem_fun( this, &TagView::handle_type_changed ) );

    m_CB_unit->signal_changed().connect(
            sigc::mem_fun( this, &TagView::handle_unit_changed ) );

    m_B_theme_reset->signal_clicked().connect(
            sigc::mem_fun( this, &TagView::reset_theme ) );

    m_B_filtering->signal_clicked().connect(
            sigc::mem_fun( this, &TagView::handle_filter_toggled ) );
}

Glib::ustring
TagView::get_info_str() const
{
    if( m_ptr2elem->get_type() == DiaryElement::ET_TAG )
    {
        switch( m_ptr2elem->get_chart_type() & ChartPoints::VALUE_TYPE_MASK )
        {
            case ChartPoints::BOOLEAN:
                return Glib::ustring::compose( _( STRING::ELEM_WITH_ENTRIES ),
                                               m_ptr2elem->get_type_name(),
                                               m_ptr2elem->get_size() );
            case ChartPoints::AVERAGE:
                return Glib::ustring::compose( _( STRING::TAG_WITH_ENTRIES_AVERAGE ),
                                               m_ptr2elem->get_size(),
                                               m_ptr2elem->get_combined_value_str() );
            case ChartPoints::CUMULATIVE:
                return Glib::ustring::compose( _( STRING::TAG_WITH_ENTRIES_CUMULATIVE ),
                                               m_ptr2elem->get_size(),
                                               m_ptr2elem->get_combined_value_str() );
        }
    }
    //else -with else we get warnings
        return _( "Filter and theme entries without tags" );
}

bool
TagView::check_title_applicable( const Glib::ustring& name ) const
{
    Tag *tag = Diary::d->get_tags()->get_tag( name );
    return( !( tag || name.empty() ) );
}

bool
TagView::apply_title( const Glib::ustring& name )
{
    // a caution against calls by an entry activation event:
    if( ! check_title_applicable( name ) )
        return false;

    Diary::d->get_tags()->rename( m_ptr2elem, name );
    AppWindow::p->panel_extra->refresh_elem( m_ptr2elem );

    return true;
}

void
TagView::handle_login()
{
    update_combobox_categories();
    m_G_general->set_visible( ! Diary::d->is_read_only() );
    m_G_theme->set_visible( ! Diary::d->is_read_only() );
}

void
TagView::handle_filter_toggled()
{
    if( Lifeograph::s_internaloperation )
        return;

    const Tag* tag( m_ptr2elem == Diary::d->get_filter_tag() ? NULL : m_ptr2elem );

    Diary::d->set_filter_tag( tag );
    AppWindow::p->panel_extra->set_filtered_tag( tag );
    AppWindow::p->panel_diary->handle_filter_changed();
}

void
TagView::handle_category_changed()
{
    if( Lifeograph::s_internaloperation )
        return;

    Gtk::TreeIter iter = m_CB_category->get_active();
    if( iter )
    {
        Gtk::TreeRow row = *iter;
        if( row[ colrec_category.type ]  == TCT_NEW ) // create new
        {
            CategoryTags *category = Diary::d->create_tag_ctg();
            m_ptr2elem->set_category( category );
            update_combobox_categories();
            AppWindow::p->panel_extra->populate();
            category->show();
            AppWindow::p->panel_main->start_title_edit();
        }
        else	// assign a category or reset
        {
            m_ptr2elem->set_category( row[ colrec_category.ptr ] );
            AppWindow::p->panel_extra->populate();
        }
    }
}

void
TagView::handle_type_changed()
{
    if( Lifeograph::s_internaloperation ) return;

    switch( m_CB_type->get_active_id()[ 0 ] )
    {
        case 'B':   // Boolean
            m_ptr2elem->set_chart_type( ChartPoints::BOOLEAN );
            m_CB_unit->set_visible( false );
            break;
        case 'C':   // Cumulative
            m_ptr2elem->set_chart_type( ChartPoints::CUMULATIVE );
            m_CB_unit->set_visible( true );
            break;
        case 'A':   // Average
            m_ptr2elem->set_chart_type( ChartPoints::AVERAGE );
            m_CB_unit->set_visible( true );
            break;
    }

    update_points( false );
}

void
TagView::handle_unit_changed()
{
    if( Lifeograph::s_internaloperation ) return;

    m_ptr2elem->set_unit( m_E_unit->get_text() );
    m_widgetchart->set_points( m_ptr2elem->create_chart_data() );
    m_Sc_chart->set_visible( m_widgetchart->is_zoom_possible() );
}

void
TagView::update_combobox_categories()
{
    m_liststore_categories->clear();

    Gtk::TreeRow row = * m_liststore_categories->append();

    row[ colrec_category.name ] = _( "None" );
    row[ colrec_category.ptr ] = NULL;
    row[ colrec_category.type ] = TCT_NONE;

    // separator 1:
    row = * m_liststore_categories->append();
    row[ colrec_category.type ] = TCT_SEPARATOR;
    row[ colrec_category.ptr ] = ( CategoryTags* ) 0x1;

    for( auto& kv_tag_ctg : Diary::d->m_tag_categories )
    {
        row = * m_liststore_categories->append();

        row[ colrec_category.name ] = kv_tag_ctg.first;
        row[ colrec_category.ptr ] = kv_tag_ctg.second;
        row[ colrec_category.type ] = TCT_CATEGORY;
    }

    // separator 2:
    if( Diary::d->m_tag_categories.size() > 0 )
    {
        row = * m_liststore_categories->append();
        row[ colrec_category.type ] = TCT_SEPARATOR;
        row[ colrec_category.ptr ] = ( CategoryTags* ) 0x1;
    }

    row = * m_liststore_categories->append();

    row[ colrec_category.name ] = _( "Create New..." );
    // a dummy value to differentiate it from "None":
    row[ colrec_category.ptr ] = ( CategoryTags* ) 0x1;
    row[ colrec_category.type ] = TCT_NEW;
}

void
TagView::update_theme_view()
{
    Theme *theme = m_ptr2elem->get_theme();

    m_textview_theme->override_font( theme->font );
    m_textview_theme->override_background_color( theme->color_base, Gtk::STATE_FLAG_NORMAL );
    m_textview_theme->override_color( theme->color_text, Gtk::STATE_FLAG_NORMAL );

    m_textview_theme->override_background_color( theme->color_heading, Gtk::STATE_FLAG_SELECTED );
    m_textview_theme->override_color( theme->color_base, Gtk::STATE_FLAG_SELECTED );

    Gdk::RGBA color_mid( midtone( theme->color_base, theme->color_text ) );

    m_tag_heading->property_foreground_rgba() = theme->color_heading;
    m_tag_subheading->property_foreground_rgba() = theme->color_subheading;
    m_tag_highlight->property_background_rgba() = theme->color_highlight;
    m_tag_background->property_foreground_rgba() = theme->color_base;
    m_tag_background->property_background_rgba() = color_mid;

    m_tag_comment->property_foreground_rgba() = color_mid;
    m_tag_region->property_paragraph_background_rgba() = midtone(
            theme->color_base, theme->color_text, 0.9 );
    m_tag_match->property_foreground_rgba() = theme->color_base;
    m_tag_match->property_background_rgba() = contrast2(
            theme->color_base, Theme::s_color_match1, Theme::s_color_match2 );
    m_tag_link->property_foreground_rgba() = contrast2(
            theme->color_base, Theme::s_color_link1, Theme::s_color_link2 );
    m_tag_link_broken->property_foreground_rgba() = contrast2(
            theme->color_base, Theme::s_color_broken1, Theme::s_color_broken2 );

    // THEME VIEW CONTENTS
    m_textview_theme->get_buffer()->set_text( "" );

    add_action_theme_text( _( "Heading" ), m_tag_heading );

    add_plain_theme_text( " \n " );

    add_action_theme_text( _( "Subheading" ), m_tag_subheading );

    add_plain_theme_text( " \n" );

    add_action_theme_text( _( "Normal text" ), m_tag_normal );

    add_plain_theme_text( "  " );

    add_action_theme_text( _( "highlighted text" ), m_tag_highlight );

    add_plain_theme_text( "  " );

    add_tagged_theme_text( Glib::ustring::format( "[[", _( "comment" ), "]]" ), m_tag_comment );

    add_plain_theme_text( "  " );

    add_tagged_theme_text( _( "link" ), m_tag_link );

    add_plain_theme_text( "  " );

    add_tagged_theme_text( _( "broken link" ), m_tag_link_broken );

    add_plain_theme_text( "  " );

    add_tagged_theme_text( _( "match" ), m_tag_match );

    add_plain_theme_text( " \n" );

    add_action_theme_text(
            Glib::ustring::format(
                    _( "Font" ), ": ", m_ptr2elem->get_theme()->font.to_string() ),
                    m_tag_font );

    add_plain_theme_text( " \n" );

    add_action_theme_text( _( "Background" ), m_tag_background );

    add_plain_theme_text( " \n" );

    add_tagged_theme_text(
            Glib::ustring::format(
                    ".\t",
                    _( "Click on the parts to edit. Non-editable parts adapt automatically." ) ),
            m_tag_region );
}

void
TagView::update_theme_menu()
{
    m_Bx_themes->foreach( []( Gtk::Widget& widget ) { delete &widget; } );

    for( auto& kv_tag : *Diary::d->get_tags() )
    {
        Tag* tag( kv_tag.second );

        if( ! tag->get_has_own_theme() )
            continue;
        if( tag == m_ptr2elem )
            continue;

        Gtk::ModelButton* MoB_theme = Gtk::manage( new Gtk::ModelButton );
        MoB_theme->set_label( tag->get_name() );
        MoB_theme->show();
        Theme *theme = tag->get_theme();

        MoB_theme->override_font( theme->font );
        MoB_theme->override_background_color( theme->color_base, Gtk::STATE_FLAG_NORMAL );
        MoB_theme->override_color( theme->color_text, Gtk::STATE_FLAG_NORMAL );

        MoB_theme->signal_clicked().connect(
                sigc::bind( sigc::mem_fun( this, &TagView::copy_theme_from ), tag ) );

        m_Bx_themes->add( *MoB_theme );
    }

    m_B_theme_reset->set_sensitive( m_ptr2elem->get_has_own_theme() );
}

void
TagView::reset_theme()
{
    m_ptr2elem->reset_theme();
    m_B_theme_reset->set_sensitive( false );
    update_theme_view();
    AppWindow::p->panel_main->set_icon( m_ptr2elem->get_icon32() );
    AppWindow::p->panel_extra->refresh_elem( m_ptr2elem );
}

void
TagView::copy_theme_from( const Tag* tag )
{
    m_ptr2elem->create_own_theme_duplicating( tag->get_theme() );
    m_B_theme_reset->set_sensitive( true );
    update_theme_view();
    AppWindow::p->panel_main->set_icon( m_ptr2elem->get_icon32() );
    AppWindow::p->panel_extra->refresh_elem( m_ptr2elem );
}

void
TagView::show( Tag& tag )
{
    // do nothing if it is already the current element:
    if( AppWindow::p->panel_main->is_cur_elem( &tag ) )
        return;

    m_ptr2elem = &tag;

    AppWindow::p->panel_main->show( this );

    Lifeograph::s_internaloperation++;

    // BODY
    for( Gtk::TreeIter iter = m_liststore_categories->children().begin();
         iter != m_liststore_categories->children().end();
         ++iter )
    {
        if( ( *iter )[ colrec_category.ptr ] == tag.get_category() )
            m_CB_category->set_active( iter );
    }

    switch( tag.get_chart_type() & ChartPoints::VALUE_TYPE_MASK )
    {
        case ChartPoints::BOOLEAN:
            m_CB_type->set_active_id( "B" );
            m_CB_unit->set_visible( false );
            break;
        case ChartPoints::CUMULATIVE:
            m_CB_type->set_active_id( "C" );
            m_CB_unit->set_visible( true );
            break;
        case ChartPoints::AVERAGE:
            m_CB_type->set_active_id( "A" );
            m_CB_unit->set_visible( true );
            break;
    }
    m_E_unit->set_text( tag.get_unit() );

    // CHART
    handle_chart_shown();

    Lifeograph::s_internaloperation--;

    m_G_general->set_visible( ! Diary::d->is_read_only() &&
                              tag.get_type() == DiaryElement::ET_TAG );

    update_theme_view();
    update_theme_menu();
}

Gtk::Popover*
TagView::get_popover()
{
    return( ( Diary::d->is_read_only() || m_ptr2elem->get_type() == DiaryElement::ET_UNTAGGED ) ?
            nullptr : m_Po_elem );
}

void
TagView::dismiss_tag()
{
    AppWindow::p->panel_main->hide_popover();

    Gtk::CheckButton check( _( "Also dismiss the entries associated with this tag" ) );
    if( ! AppWindow::p->confirm_dismiss_element( m_ptr2elem, &check ) )
        return;

    Diary::d->show();

    AppWindow::p->panel_main->remove_element_from_history( m_ptr2elem );

    // clear filter if necessary:
    if( Diary::d->get_filter_tag() == m_ptr2elem )
    {
        Diary::d->set_filter_tag( NULL );
    }

    Diary::d->dismiss_tag( m_ptr2elem, check.get_active() );
    AppWindow::p->panel_diary->update_entry_list();
    AppWindow::p->panel_extra->populate();
}

bool
TagView::is_row_separator( const Glib::RefPtr< Gtk::TreeModel >& model,
                             const Gtk::TreeIter iter )
{
    Gtk::TreeRow row = * iter;
    return ( row[ colrec_category.type ] == TCT_SEPARATOR );
}

void
TagView::handle_theme_motion_notify_event( GdkEventMotion* event )
{
    Gtk::TextIter       iter;
    int                 trailing, buffer_x, buffer_y;
    static const Glib::RefPtr< Gdk::Cursor > cursor_hand( Gdk::Cursor::create( Gdk::HAND2 ) );
    static const Glib::RefPtr< Gdk::Cursor > cursor_xterm( Gdk::Cursor::create( Gdk::XTERM ) );

    m_textview_theme->window_to_buffer_coords( Gtk::TEXT_WINDOW_WIDGET,
                                               event->x, event->y, buffer_x, buffer_y );
    m_textview_theme->get_iter_at_position( iter, trailing, buffer_x, buffer_y );

    if( iter.has_tag( m_tag_action ) )
        m_textview_theme->get_window( Gtk::TEXT_WINDOW_TEXT )->set_cursor( cursor_hand );
    else
        m_textview_theme->get_window( Gtk::TEXT_WINDOW_TEXT )->set_cursor( cursor_xterm );
}

void
TagView::handle_theme_button_release_event( GdkEventButton* event )
{
    Gtk::TextIter       iter;
    int                 trailing, buffer_x, buffer_y;
    m_textview_theme->window_to_buffer_coords( Gtk::TEXT_WINDOW_WIDGET,
                                               event->x, event->y, buffer_x, buffer_y );
    m_textview_theme->get_iter_at_position( iter, trailing, buffer_x, buffer_y );

    if( iter.has_tag( m_tag_action ) )
    {
        if( !m_ptr2elem->get_has_own_theme() )
            m_ptr2elem->create_own_theme_duplicating( ThemeSystem::get() );
        Theme* theme = m_ptr2elem->get_theme();

        if( iter.has_tag( m_tag_heading ) )
            launch_color_dialog( theme->color_heading );
        else if( iter.has_tag( m_tag_subheading ) )
            launch_color_dialog( theme->color_subheading );
        else if( iter.has_tag( m_tag_normal ) )
            launch_color_dialog( theme->color_text );
        else if( iter.has_tag( m_tag_highlight ) )
            launch_color_dialog( theme->color_highlight );
        else if( iter.has_tag( m_tag_background ) )
            launch_color_dialog( theme->color_base );
        else if( iter.has_tag( m_tag_font ) )
            launch_font_dialog();

        m_B_theme_reset->set_sensitive( true );
        AppWindow::p->panel_main->set_icon( m_ptr2elem->get_icon32() );
        AppWindow::p->panel_extra->refresh_elem( m_ptr2elem );
    }
}

void
TagView::add_action_theme_text( const Glib::ustring& text,
                                const Glib::RefPtr< Gtk::TextTag >& tag )
{
    std::vector< Glib::RefPtr< Gtk::TextTag > > tags;
    tags.push_back( m_tag_action );
    tags.push_back( tag );

    m_textview_theme->get_buffer()->insert_with_tags(
            m_textview_theme->get_buffer()->end(), text, tags );
}
void
TagView::add_tagged_theme_text( const Glib::ustring& text,
                                const Glib::RefPtr< Gtk::TextTag >& tag )
{
    m_textview_theme->get_buffer()->insert_with_tag(
            m_textview_theme->get_buffer()->end(), text, tag );
}
void
TagView::add_plain_theme_text( const Glib::ustring& text )
{
    m_textview_theme->get_buffer()->insert( m_textview_theme->get_buffer()->end(), text );
}

void
TagView::launch_color_dialog( Gdk::RGBA& color )
{
    Gtk::ColorChooserDialog *dlg = new Gtk::ColorChooserDialog;
    dlg->set_transient_for( * AppWindow::p );
    dlg->set_use_alpha( false );
    dlg->set_rgba( color );

    if( dlg->run() == Gtk::RESPONSE_OK )
    {
        color = dlg->get_rgba();
        update_theme_view();
    }

    delete dlg;
}

void
TagView::launch_font_dialog()
{
    Gtk::FontChooserDialog *dlg = new Gtk::FontChooserDialog;
    dlg->set_transient_for( * AppWindow::p );
    dlg->set_font_desc( m_ptr2elem->get_theme()->font );

    if( dlg->run() == Gtk::RESPONSE_OK )
    {
        m_ptr2elem->get_theme()->font = dlg->get_font_desc();
        update_theme_view();
    }

    delete dlg;
}

// TAG CATEGORY ====================================================================================
CategoryTagsView::CategoryTagsView()
{
    CategoryTags::shower = this;

    try
    {
        Lifeograph::get_builder()->get_widget( "Bx_categorytags", m_box );
        Lifeograph::get_builder()->get_widget( "Po_tagctg", m_Po_elem );
        Lifeograph::get_builder()->get_widget( "B_tagctg_dismiss", m_B_dismiss );
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create the tag category view" );
    }

    m_B_dismiss->signal_clicked().connect(
            sigc::mem_fun( this, &CategoryTagsView::dismiss_category ) );
}

void
CategoryTagsView::show( CategoryTags& category )
{
    // do nothing if it is already the current element or when in read-only mode:
    if( AppWindow::p->panel_main->is_cur_elem( &category ) ||
        Diary::d->is_read_only() )
        return;

    m_ptr2elem = &category;

    AppWindow::p->panel_main->show( this );
}

Gtk::Popover*
CategoryTagsView::get_popover()
{
    return( Diary::d->is_read_only() ? nullptr : m_Po_elem );
}

bool
CategoryTagsView::check_title_applicable( const Glib::ustring& name ) const
{
    PoolCategoriesTags::const_iterator iter = Diary::d->m_tag_categories.find( name );
    return( iter == Diary::d->m_tag_categories.end() && name.empty() == false );
}

bool
CategoryTagsView::apply_title( const Glib::ustring& name )
{
    if( ! check_title_applicable( name ) )
        return false;

    Diary::d->m_tag_categories.rename_category( m_ptr2elem, name );
    AppWindow::p->panel_extra->populate();
    AppWindow::p->m_tag_view->update_combobox_categories();

    return true;
}

void
CategoryTagsView::dismiss_category()
{
    AppWindow::p->panel_main->hide_popover();

    Gtk::CheckButton check( _( "Also dismiss the tags in the category" ) );
    if( ! AppWindow::p->confirm_dismiss_element( m_ptr2elem, &check ) )
        return;

    Diary::d->show();

    AppWindow::p->panel_main->remove_element_from_history( m_ptr2elem );
    Diary::d->dismiss_tag_ctg( m_ptr2elem, check.get_active() );
    AppWindow::p->panel_extra->populate();
    AppWindow::p->m_tag_view->update_combobox_categories();
}

// CHAPTER =========================================================================================
ChapterView::ChapterView()
:   ElementViewWithChart( "chapter" )
{
    Chapter::shower = this;

    try
    {
        auto builder{ Lifeograph::get_builder() };
        builder->get_widget( "Bx_chapter", m_box );
        builder->get_widget( "Po_chapter", m_Po_elem );
        builder->get_widget( "I_chapter_not_todo", m_I_not_todo );
        builder->get_widget( "RB_chapter_not_todo", m_RB_not_todo );
        builder->get_widget( "RB_chapter_todo", m_RB_todo );
        builder->get_widget( "RB_chapter_progressed", m_RB_progressed );
        builder->get_widget( "RB_chapter_done", m_RB_done );
        builder->get_widget( "RB_chapter_canceled", m_RB_canceled );
        builder->get_widget( "ClB_chapter_color", m_ClB_color );
        builder->get_widget( "B_chapter_dismiss", m_B_dismiss );

//		builder->get_widget( "label_chapter_period", m_label_period );
//		builder->get_widget( "hbox_chapter_period", m_hbox_period );
        builder->get_widget( "E_chapter_begins", m_E_begins );
        builder->get_widget( "B_chapter_date_apply", m_B_date_apply );
        builder->get_widget( "L_chapter_ends", m_L_ends );

        builder->get_widget( "G_chapter_edit", m_G_edit );
        builder->get_widget( "G_chapter_actions", m_G_actions );
        builder->get_widget( "G_chapter_chart", m_G_chart );
        builder->get_widget( "B_chapter_new_entry", m_B_new_entry );

        // THEMABLE ICONS
        Gtk::Image* img;
        builder->get_widget( "I_chapter_todo", img );
        img->set( Lifeograph::icons->todo_open_32 );
        builder->get_widget( "I_chapter_progressed", img );
        img->set( Lifeograph::icons->todo_progressed_32 );
        builder->get_widget( "I_chapter_done", img );
        img->set( Lifeograph::icons->todo_done_32 );
        builder->get_widget( "I_chapter_canceled", img );
        img->set( Lifeograph::icons->todo_canceled_32 );
    }
    catch( ... )
    {
        throw HELPERS::Error( "Failed to create the chapter view" );
    }

    // SIGNALS
    m_RB_not_todo->signal_toggled().connect(
            sigc::bind( sigc::mem_fun( this, &ChapterView::set_todo_status ), ES::NOT_TODO ) );
    m_RB_todo->signal_toggled().connect(
            sigc::bind( sigc::mem_fun( this, &ChapterView::set_todo_status ), ES::TODO ) );
    m_RB_progressed->signal_toggled().connect(
            sigc::bind( sigc::mem_fun( this, &ChapterView::set_todo_status ), ES::PROGRESSED ) );
    m_RB_done->signal_toggled().connect(
            sigc::bind( sigc::mem_fun( this, &ChapterView::set_todo_status ), ES::DONE ) );
    m_RB_canceled->signal_toggled().connect(
            sigc::bind( sigc::mem_fun( this, &ChapterView::set_todo_status ), ES::CANCELED ) );

    m_B_dismiss->signal_clicked().connect( sigc::mem_fun( this, &ChapterView::dismiss_chapter ) );

    m_E_begins->signal_changed().connect(
            sigc::mem_fun( *this, &ChapterView::handle_date_changed ) );
    m_E_begins->signal_activate().connect(
            sigc::mem_fun( *m_B_date_apply, &Gtk::Button::clicked ) );
    m_B_date_apply->signal_clicked().connect(
            sigc::mem_fun( *this, &ChapterView::handle_date_applied ) );

    m_ClB_color->signal_color_set().connect(
                sigc::mem_fun( *this, &ChapterView::handle_color_changed ) );

    m_B_new_entry->signal_clicked().connect( sigc::mem_fun( *this, &ChapterView::add_new_entry ) );
}

void
ChapterView::dismiss_chapter()
{
    AppWindow::p->panel_main->hide_popover();

    Gtk::CheckButton check( _( "Also dismiss the entries within" ) );
    if( ! AppWindow::p->confirm_dismiss_element( m_ptr2elem, &check ) )
        return;

    Diary::d->show();

    AppWindow::p->panel_main->remove_element_from_history( m_ptr2elem );
    Diary::d->dismiss_chapter( m_ptr2elem, check.get_active() );
    AppWindow::p->panel_diary->update_entry_list();
}

bool
ChapterView::check_title_applicable( const Glib::ustring& str ) const
{
    return( ! str.empty() && str != m_ptr2elem->m_name );
}

bool
ChapterView::apply_title( const Glib::ustring& str )
{
    // a caution against calls by an entry activation event:
    if( ! check_title_applicable( str ) )
        return false;
    m_ptr2elem->set_name( str );
    AppWindow::p->panel_diary->handle_elem_changed( m_ptr2elem );
    return true;
}

void
ChapterView::handle_date_changed()
{
    Date date( m_E_begins->get_text() );

    if( date.is_set() && ! date.is_ordinal() )
        m_B_date_apply->set_sensitive(
                ! Diary::d->get_current_chapter_ctg()->get_chapter( date.m_date ) );
    else
        m_B_date_apply->set_sensitive( false );
}

void
ChapterView::handle_date_applied()
{
    // a caution against calls by an entry activation event:
    if( ! m_B_date_apply->is_sensitive() )
        return;

    Date date( m_E_begins->get_text() );

    if( date.is_set() &&
        Diary::d->get_current_chapter_ctg()->set_chapter_date( m_ptr2elem, date.m_date ) )
    {
        m_B_date_apply->set_sensitive( false );
        Diary::d->update_entries_in_chapters();
        AppWindow::p->panel_diary->update_entry_list();
        update_labels();
        update_points();
    }
}

void
ChapterView::handle_color_changed()
{
    m_ptr2elem->m_color = m_ClB_color->get_rgba();
    update_points();
}

void
ChapterView::update_labels()
{
    if( ! m_ptr2elem->m_date_begin.is_ordinal() )
    {
        Lifeograph::s_internaloperation++;

        m_E_begins->set_text( m_ptr2elem->m_date_begin.format_string() );

        CategoryChapters::iterator iter( Diary::d->get_current_chapter_ctg()->find(
                                                m_ptr2elem->get_date_t() ) );
        if( m_ptr2elem->m_time_span == 0 )
            m_L_ends->set_text( _( "Unlimited" ) );
        else
        {
            --iter;
            m_L_ends->set_text( iter->second->m_date_begin.format_string() );
        }

        Lifeograph::s_internaloperation--;
    }
}

void
ChapterView::add_new_entry()
{
    Diary::d->create_entry( m_ptr2elem->get_free_order().m_date );

    AppWindow::p->panel_diary->update_entry_list();
    AppWindow::p->panel_diary->update_calendar();
}

void
ChapterView::handle_login()
{
    bool editable( ! Diary::d->is_read_only() );
    m_G_edit->set_visible( editable );
    m_B_date_apply->set_visible( editable );
    m_G_actions->set_visible( editable );
}

void
ChapterView::show( Chapter& chapter )
{

    const bool flag_ordinal{ chapter.is_ordinal() };
    const bool flag_read_only{ Diary::d->is_read_only() };
    // do nothing if entry is already the current element or
    // there is nothing to do due to read-only mode:
    if( AppWindow::p->panel_main->is_cur_elem( &chapter ) ||
        ( flag_read_only && flag_ordinal ) )
        return;

    m_ptr2elem = &chapter;

    AppWindow::p->panel_main->show( this );

    // BODY
    update_labels();
    if( ! flag_read_only )
    {
        m_G_edit->set_visible( ! flag_ordinal );
        m_G_actions->set_visible( flag_ordinal );
    }
    m_G_chart->set_visible( ! flag_ordinal );

    m_I_not_todo->set( chapter.get_date_icon( true ) );

    switch( m_ptr2elem->get_todo_status_effective() )
    {
        case ES::NOT_TODO:
            m_RB_not_todo->set_active();
            break;
        case ES::TODO:
            m_RB_todo->set_active();
            break;
        case ES::PROGRESSED:
            m_RB_progressed->set_active();
            break;
        case ES::DONE:
            m_RB_done->set_active();
            break;
        case ES::CANCELED:
            m_RB_canceled->set_active();
            break;
    }

    m_ClB_color->set_rgba( chapter.m_color );

    // CHART DATA
    if( ! flag_ordinal )
        handle_chart_shown();
}

Gtk::Popover*
ChapterView::get_popover()
{
    return( Diary::d->is_read_only() ? nullptr : m_Po_elem );
}

void
ChapterView::set_todo_status( ElemStatus status )
{
    if( Lifeograph::s_internaloperation )
        return;

    m_ptr2elem->set_todo_status( status );
    AppWindow::p->panel_main->set_icon( m_ptr2elem->get_icon32() );
    AppWindow::p->panel_diary->handle_elem_changed( m_ptr2elem );
}

// FILTER ==========================================================================================
FilterView::ColrecEntries*  FilterView::colrec_entries = new ColrecEntries;

FilterView::FilterView()
{
    try
    {
        auto builder{ Lifeograph::get_builder() };
        builder->get_widget( "TB_filter_not_todo", m_TB_todo_not );
        builder->get_widget( "TB_filter_todo", m_TB_todo_open );
        builder->get_widget( "TB_filter_progressed", m_TB_todo_progressed );
        builder->get_widget( "TB_filter_done", m_TB_todo_done );
        builder->get_widget( "TB_filter_canceled", m_TB_todo_canceled );
        builder->get_widget( "TB_filter_not_favored", m_TB_not_favored );
        builder->get_widget( "TB_filter_favored", m_TB_favored );
        builder->get_widget( "TB_filter_not_trashed", m_TB_not_trashed );
        builder->get_widget( "TB_filter_trashed", m_TB_trashed );

        builder->get_widget( "L_filter_tag", m_L_tag );
        builder->get_widget( "B_filter_clear_tag", m_B_clear_tag );

        builder->get_widget_derived( "E_filter_date_start", m_E_date_begin );
        builder->get_widget_derived( "E_filter_date_end", m_E_date_end );

        builder->get_widget( "TV_filter_entries", m_TV_entries );
        builder->get_widget( "B_filter_remove_entry", m_B_remove_entry );
        builder->get_widget( "B_filter_rm_all_entries", m_B_rm_all_entries );

        builder->get_widget( "B_filter_make_default", m_B_make_default );
        builder->get_widget( "B_filter_revert", m_B_revert );

        m_LS_entries = Gtk::ListStore::create( *colrec_entries );

        // THEMABLE ICONS
        Gtk::Image* img;
        builder->get_widget( "I_filter_not_todo", img );
        img->set( Lifeograph::icons->entry_16 );
        builder->get_widget( "I_filter_todo", img );
        img->set( Lifeograph::icons->todo_open_16 );
        builder->get_widget( "I_filter_progressed", img );
        img->set( Lifeograph::icons->todo_progressed_16 );
        builder->get_widget( "I_filter_done", img );
        img->set( Lifeograph::icons->todo_done_16 );
        builder->get_widget( "I_filter_canceled", img );
        img->set( Lifeograph::icons->todo_canceled_16 );
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create the filter view" );
    }

    m_B_make_default->signal_clicked().connect(
            sigc::mem_fun( this, &FilterView::make_default ) );

    // revert filter clicks are also handled here in addition to PanelDiary, the main handler
    m_B_revert->signal_clicked().connect(
                sigc::mem_fun( this, &FilterView::update ) );

    m_TB_todo_not->signal_toggled().connect(
            sigc::mem_fun( this, &FilterView::handle_todo_changed ) );
    m_TB_todo_open->signal_toggled().connect(
            sigc::mem_fun( this, &FilterView::handle_todo_changed ) );
    m_TB_todo_progressed->signal_toggled().connect(
            sigc::mem_fun( this, &FilterView::handle_todo_changed ) );
    m_TB_todo_done->signal_toggled().connect(
            sigc::mem_fun( this, &FilterView::handle_todo_changed ) );
    m_TB_todo_canceled->signal_toggled().connect(
            sigc::mem_fun( this, &FilterView::handle_todo_changed ) );

    m_TB_trashed->signal_toggled().connect(
            sigc::mem_fun( this, &FilterView::handle_trash_changed ) );
    m_TB_not_trashed->signal_toggled().connect(
            sigc::mem_fun( this, &FilterView::handle_trash_changed ) );

    m_TB_favored->signal_toggled().connect(
            sigc::mem_fun( this, &FilterView::handle_favored_changed ) );
    m_TB_not_favored->signal_toggled().connect(
            sigc::mem_fun( this, &FilterView::handle_favored_changed ) );

    m_L_tag->drag_dest_set( Lifeograph::p->drag_targets_tag );
    m_L_tag->signal_drag_drop().connect(
            sigc::mem_fun( this, &FilterView::handle_tag_dropped ) );
    m_B_clear_tag->signal_clicked().connect(
            sigc::mem_fun( this, &FilterView::clear_tag ) );

    m_E_date_begin->signal_changed().connect(
            sigc::mem_fun( this, &FilterView::handle_date_begin_changed ) );
    m_E_date_end->signal_changed().connect(
            sigc::mem_fun( this, &FilterView::handle_date_end_changed ) );

    m_TV_entries->set_model( m_LS_entries );
    m_TV_entries->append_column(
            * Gtk::manage( new Gtk::TreeView::Column(
                    _( "Hidden Entries" ), colrec_entries->name ) ) );
    m_TV_entries->drag_dest_set( Lifeograph::p->drag_targets_entry );
    m_TV_entries->signal_drag_drop().connect(
            sigc::mem_fun( this, &FilterView::handle_entry_dropped ) );
    m_TV_entries->get_selection()->signal_changed().connect(
            sigc::mem_fun( this, &FilterView::handle_entry_selection_changed ) );

    m_B_remove_entry->signal_clicked().connect(
            sigc::mem_fun( this, &FilterView::remove_selected_entries ) );
    m_B_rm_all_entries->signal_clicked().connect(
            sigc::mem_fun( this, &FilterView::remove_all_entries ) );
}

void
FilterView::handle_login()
{
    m_B_make_default->set_visible( ! Diary::d->is_read_only() );
    m_ptr2elem = Diary::d->get_filter();
    // the above line prevents segfaults when filter is reverted before being shown
}

/*void
FilterView::show( Filter& filter )
{
    // do nothing is entry is already the current element:
    if( AppWindow::p->panel_main->is_cur_elem( &filter ) )
        return;

    m_ptr2elem = &filter;

    AppWindow::p->panel_main->show( this );

    update();

}*/

void
FilterView::update()
{
    ElemStatus fs( m_ptr2elem->get_status() );

    Lifeograph::s_internaloperation++;

    // STATUS
    m_TB_favored->set_active( fs & ES::SHOW_FAVORED );
    m_TB_not_favored->set_active( fs & ES::SHOW_NOT_FAVORED );
    m_TB_trashed->set_active( fs & ES::SHOW_TRASHED );
    m_TB_not_trashed->set_active( fs & ES::SHOW_NOT_TRASHED );
    m_TB_todo_not->set_active( fs & ES::SHOW_NOT_TODO );
    m_TB_todo_open->set_active( fs & ES::SHOW_TODO );
    m_TB_todo_progressed->set_active( fs & ES::SHOW_PROGRESSED );
    m_TB_todo_done->set_active( fs & ES::SHOW_DONE );
    m_TB_todo_canceled->set_active( fs & ES::SHOW_CANCELED );

    // TAG
    if( m_ptr2elem->get_tag() )
        m_L_tag->set_text( m_ptr2elem->get_tag()->get_name() );
    else
        m_L_tag->set_text( _( STRING::DROP_TAG_TO_FILTER ) );

    m_B_clear_tag->set_visible( m_ptr2elem->get_tag() );

    // DATE INTERVAL
    if( m_ptr2elem->get_date_begin() > 0 )
        m_E_date_begin->set_text( Date::format_string( m_ptr2elem->get_date_begin() ) );
    else
        m_E_date_begin->set_text( "" );

    if( m_ptr2elem->get_date_end() != Date::DATE_MAX )
        m_E_date_end->set_text( Date::format_string( m_ptr2elem->get_date_end() ) );
    else
        m_E_date_end->set_text( "" );

    // INDIVIDUAL ENTRIES
    update_entries();

    Lifeograph::s_internaloperation--;
}

/*Glib::ustring
FilterView::get_info_str() const
{
    return _( "Determines the entries to be shown in the list" );
}*/

void
FilterView::make_default()
{
    AppWindow::p->panel_main->hide_popover();

    Diary::d->get_filter_default()->set( m_ptr2elem );
    m_B_revert->set_visible( false );
}

void
FilterView::handle_trash_changed()
{
    if( Lifeograph::s_internaloperation )
        return;

    bool trashed( m_TB_trashed->get_active() );
    bool not_trashed( m_TB_not_trashed->get_active() );

    if( !( trashed || not_trashed ) )
    {
        if( m_ptr2elem->get_status() & ES::SHOW_TRASHED )
            not_trashed = true;
        if( m_ptr2elem->get_status() & ES::SHOW_NOT_TRASHED )
            trashed = true;

        Lifeograph::s_internaloperation++;
        m_TB_not_trashed->set_active( not_trashed );
        m_TB_trashed->set_active( trashed );
        Lifeograph::s_internaloperation--;
    }

    m_ptr2elem->set_trash( trashed, not_trashed );
    AppWindow::p->panel_diary->handle_filter_changed();
}

void
FilterView::handle_favored_changed()
{
    if( Lifeograph::s_internaloperation )
        return;

    bool favored( m_TB_favored->get_active() );
    bool not_favored( m_TB_not_favored->get_active() );

    if( !( favored || not_favored ) )
    {
        if( m_ptr2elem->get_status() & ES::SHOW_FAVORED )
            not_favored = true;
        if( m_ptr2elem->get_status() & ES::SHOW_NOT_FAVORED )
            favored = true;

        Lifeograph::s_internaloperation++;
        m_TB_favored->set_active( favored );
        m_TB_not_favored->set_active( not_favored );
        Lifeograph::s_internaloperation--;
    }

    m_ptr2elem->set_favorites( favored, not_favored );
    AppWindow::p->panel_diary->handle_filter_changed();
}

void
FilterView::handle_todo_changed()
{
    if( Lifeograph::s_internaloperation )
        return;

    m_ptr2elem->set_todo( m_TB_todo_not->get_active(),
                          m_TB_todo_open->get_active(),
                          m_TB_todo_progressed->get_active(),
                          m_TB_todo_done->get_active(),
                          m_TB_todo_canceled->get_active() );

    AppWindow::p->panel_diary->handle_filter_changed();
}

void
FilterView::clear_tag()
{
    m_L_tag->set_text( _( STRING::DROP_TAG_TO_FILTER ) );
    m_B_clear_tag->hide();
    m_ptr2elem->set_tag( NULL );
    AppWindow::p->panel_extra->set_filtered_tag( NULL );
    AppWindow::p->panel_diary->handle_filter_changed();
}

bool
FilterView::handle_tag_dropped( const Glib::RefPtr< Gdk::DragContext >& context,
                                int x, int y, guint time )
{
    bool retval( true );

    if( Lifeograph::s_elem_dragged != NULL )
    {
        switch( Lifeograph::s_elem_dragged->get_type() )
        {
            case DiaryElement::ET_TAG:
            case DiaryElement::ET_UNTAGGED:
            {
                const Tag *tag( dynamic_cast< Tag* >( Lifeograph::s_elem_dragged ) );
                m_L_tag->set_text( Lifeograph::s_elem_dragged->get_name() );
                m_B_clear_tag->show();
                m_ptr2elem->set_tag( tag );
                AppWindow::p->panel_extra->set_filtered_tag( tag );
                AppWindow::p->panel_diary->handle_filter_changed();
            }
                break;
            default:
                retval = false;
                break;
        }
    }

    context->drag_finish( retval, false, time );

    return retval;
}

void
FilterView::handle_date_begin_changed()
{
    if( Lifeograph::s_internaloperation )
        return;

    m_E_date_begin->unset_color();

    if( m_E_date_begin->get_text_length() == 0 )
    {
        Diary::d->set_filter_date_begin( 0 );
    }
    else
    {
        Date date( m_E_date_begin->get_text() );

        if( date.is_set() )
            Diary::d->set_filter_date_begin( date.m_date );
        else
            m_E_date_begin->override_color( Gdk::RGBA( "Red" ) );
    }

    AppWindow::p->panel_diary->handle_filter_changed();
}

void
FilterView::handle_date_end_changed()
{
    if( Lifeograph::s_internaloperation )
        return;

    m_E_date_end->unset_color();

    if( m_E_date_end->get_text_length() == 0 )
    {
        Diary::d->set_filter_date_end( Date::DATE_MAX );
    }
    else
    {
        Date date( m_E_date_end->get_text() );

        if( date.is_set() )
            Diary::d->set_filter_date_end( date.m_date );
        else
            m_E_date_end->override_color( Gdk::RGBA( "Red" ) );
    }

    AppWindow::p->panel_diary->handle_filter_changed();
}

void
FilterView::update_entries()
{
    m_LS_entries->clear();
    for( Entry* entry : *m_ptr2elem->get_entries() )
    {
        Gtk::TreeModel::Row row( *( m_LS_entries->append() ) );
        row[ colrec_entries->name ] = entry->get_name();
        row[ colrec_entries->ptr ] = entry;
    }

    m_B_rm_all_entries->set_sensitive( ! m_LS_entries->children().empty( ) );
}

void
FilterView::remove_selected_entries()
{
    std::vector< Gtk::TreePath > paths(
            m_TV_entries->get_selection()->get_selected_rows() );

    for( Gtk::TreePath& path : paths )
    {
        Gtk::TreeModel::Row row( * m_LS_entries->get_iter( path) );
        Entry* entry( row[ colrec_entries->ptr ] );
        m_ptr2elem->remove_entry( entry );
    }

    update_entries();
    AppWindow::p->panel_diary->handle_filter_changed();
}

void
FilterView::remove_all_entries()
{
    m_ptr2elem->clear_entries();
    update_entries();
    AppWindow::p->panel_diary->handle_filter_changed();
}

bool
FilterView::handle_entry_dropped( const Glib::RefPtr< Gdk::DragContext >& context,
                                  int x, int y, guint time )
{
    bool retval( true );

    if( Lifeograph::s_elem_dragged != NULL )
    {
        switch( Lifeograph::s_elem_dragged->get_type() )
        {
            case DiaryElement::ET_ENTRY:
            {
                Entry* entry( dynamic_cast< Entry* >( Lifeograph::s_elem_dragged ) );
                m_ptr2elem->add_entry( entry );
                update_entries();
                AppWindow::p->panel_diary->handle_filter_changed();
                break;
            }
            case DiaryElement::ET_MULTIPLE_ENTRIES:
            {
                sigc::slot< void, Entry* > s = sigc::mem_fun( *m_ptr2elem, &Filter::add_entry );
                AppWindow::p->panel_diary->do_for_each_selected_entry( s );
                update_entries();
                AppWindow::p->panel_diary->handle_filter_changed();
                break;
            }
            default:
                retval = false;
                break;
        }
    }

    context->drag_finish( retval, false, time );

    return retval;
}

void
FilterView::handle_entry_selection_changed()
{
    m_B_remove_entry->set_sensitive(
            m_TV_entries->get_selection()->count_selected_rows() > 0 );
}

// DIARY ===========================================================================================
DiaryView::DiaryView()
:   ElementViewWithChart( "diary" )
{
    Diary::shower = this;
    m_ptr2elem = Diary::d;	// for now no other diary is shown in the view

    Gtk::ModelButton*   B_export( NULL );
    Gtk::EventBox*      EB_startup_elem_drop;

    try
    {
        auto builder{ Lifeograph::get_builder() };
        builder->get_widget( "Bx_diary", m_box );
        builder->get_widget( "Po_diary", m_Po_elem );
        builder->get_widget( "B_diary_import", m_B_import );
        builder->get_widget( "B_diary_export", B_export );
        builder->get_widget( "S_diary_encryption", m_S_encryption );
        builder->get_widget( "B_diary_encryption", m_B_encryption );

        builder->get_widget( "L_diary_path", m_L_path );
        builder->get_widget( "CB_diary_spellcheck", m_CB_spellcheck );

        builder->get_widget( "CB_diary_startup_type", m_CB_startup_type );
        builder->get_widget( "L_diary_startup_elem", m_L_startup_elem );
        builder->get_widget( "EB_diary_startup_elem_drop", EB_startup_elem_drop );
        builder->get_widget( "L_diary_startup_elem_drop", m_L_startup_elem_drop );

        builder->get_widget( "B_diary_chapter_ctg_dismiss", m_B_chapter_ctg_dismiss );
        builder->get_widget( "B_diary_chapter_ctg_rename", m_B_chapter_ctg_rename );
        builder->get_widget( "CB_diary_chapter_ctg", m_CB_chapter_ctg );

        m_liststore_startup_type = Gtk::ListStore::create( colrec_startup_type );
        m_liststore_chapter_ctg = Gtk::ListStore::create( colrec_chapter_ctg );
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create the diary view" );
    }

    // STARTUP
    m_CB_startup_type->set_model( m_liststore_startup_type );
    m_CB_startup_type->pack_start( colrec_startup_type.name );
    Gtk::TreeRow row( * m_liststore_startup_type->append() );
    row[ colrec_startup_type.name ] = _( "Show Most Current Entry" );
    row[ colrec_startup_type.type ] = HOME_CURRENT_ELEM;
    row = * m_liststore_startup_type->append();
    row[ colrec_startup_type.name ] = _( "Remember Last Item" );
    row[ colrec_startup_type.type ] = HOME_LAST_ELEM;
    row = * m_liststore_startup_type->append();
    row[ colrec_startup_type.name ] = _( "Always Show a Fixed Item" );
    row[ colrec_startup_type.type ] = DEID_DIARY;

    EB_startup_elem_drop->drag_dest_set( Lifeograph::p->drag_targets_entry );

    // CHAPTER CATEGORIES
    m_CB_chapter_ctg->set_model( m_liststore_chapter_ctg );
    m_CB_chapter_ctg->set_entry_text_column( colrec_chapter_ctg.name );
    m_CB_chapter_ctg->set_row_separator_func(
            sigc::mem_fun( this, &DiaryView::is_row_separator ) );

    // SIGNALS
    m_CB_spellcheck->signal_changed().connect(
            sigc::mem_fun( this, &DiaryView::handle_language_changed ) );

    m_CB_startup_type->signal_changed().connect(
            sigc::mem_fun( this, &DiaryView::handle_startup_type_changed ) );
    EB_startup_elem_drop->signal_drag_motion().connect(
            sigc::mem_fun( this, &DiaryView::handle_startup_elem_drag_motion ), false );
    EB_startup_elem_drop->signal_drag_drop().connect(
            sigc::mem_fun( this, &DiaryView::handle_startup_elem_dropped ) );
    m_L_startup_elem_drop->signal_activate_link().connect(
            sigc::mem_fun( this, &DiaryView::go_to_startup_elem ), false );

    m_CB_chapter_ctg->signal_changed().connect(
            sigc::mem_fun( this, &DiaryView::handle_cur_chapter_ctg_changed ) );
    m_CB_chapter_ctg->get_entry()->signal_activate().connect(
            sigc::mem_fun( this, &DiaryView::rename_cur_chapter_ctg ) );
    m_B_chapter_ctg_rename->signal_clicked().connect(
            sigc::mem_fun( this, &DiaryView::rename_cur_chapter_ctg ) );
    m_B_chapter_ctg_dismiss->signal_clicked().connect(
            sigc::mem_fun( this, &DiaryView::dismiss_cur_chapter_ctg ) );

    m_B_import->signal_clicked().connect(
            sigc::mem_fun( *this, &DiaryView::start_dialog_import ) );
    B_export->signal_clicked().connect(
            sigc::mem_fun( *this, &DiaryView::start_dialog_export ) );
    m_B_encryption->signal_clicked().connect(
            sigc::mem_fun( *this, &DiaryView::start_dialog_password ) );
}

DiaryView::~DiaryView()
{
    Diary::shower = NULL;
}

void
DiaryView::handle_login()
{
    Lifeograph::s_internaloperation++;

    // SPELL CHECKING
    m_CB_spellcheck->remove_all();
    m_CB_spellcheck->append( _( STRING::OFF ) );
    for( const std::string& lang : Lifeograph::s_lang_list )
    {
        m_CB_spellcheck->append( lang );
    }

    if( Diary::d->m_language.empty() )
        m_CB_spellcheck->set_active_text( _( STRING::OFF ) );
    else
    {
        if( Lifeograph::s_lang_list.find( Diary::d->m_language ) ==
            Lifeograph::s_lang_list.end() )
            m_CB_spellcheck->append( Diary::d->m_language );

        m_CB_spellcheck->set_active_text( Diary::d->m_language );
    }

    update_combobox_chapter_ctg();
    if( Diary::d->m_startup_elem_id < DEID_DIARY )
        m_CB_startup_type->set_active( Diary::d->m_startup_elem_id - 1 );
    else
        m_CB_startup_type->set_active( 2 );

    Lifeograph::s_internaloperation--;

    bool editable( ! Diary::d->is_read_only() );
    m_CB_startup_type->set_sensitive( editable );
    m_CB_spellcheck->set_sensitive( editable );
    m_CB_chapter_ctg->set_sensitive( editable );

    m_B_import->set_visible( editable );
    m_S_encryption->set_visible( editable );
    m_B_encryption->set_visible( editable );

    m_B_encryption->set_label( Diary::d->m_passphrase.empty() ? _( "Encrypt..." ) :
                                                _( STRING::CHANGE_PASSWORD ) );

    // BODY
    m_L_path->set_markup( Glib::ustring::compose( "<a href=\"file://%1\">%1</a>",
            Glib::path_get_dirname( Diary::d->m_path ) ) );
}

void
DiaryView::show( Diary& diary )
{
    // do nothing is entry is already the current element:
    if( AppWindow::p->panel_main->is_cur_elem( &diary ) )
        return;

    // not necessary for now as there is only one diary to show in the view:
    // m_ptr2elem = &diary;

    AppWindow::p->panel_main->show( this );

    // CHART DATA
    handle_chart_shown();
}

void
DiaryView::start_dialog_password()
{
    AppWindow::p->panel_main->hide_popover();

    if( DialogPassword::launch(
            m_ptr2elem->is_encrypted() ? DialogPassword::OT_CHANGE : DialogPassword::OT_ADD,
            m_ptr2elem ) == RESPONSE_GO )
    {
        AppWindow::p->panel_main->refresh_extra_info();
        m_B_encryption->set_label( _( STRING::CHANGE_PASSWORD ) );
    }
}

void
DiaryView::start_dialog_import()
{
    AppWindow::p->panel_main->hide_popover();

    if( m_dialog_import == nullptr )
        Lifeograph::get_builder2()->get_widget_derived( "D_import",
                                                        m_dialog_import );

    if(  m_dialog_import->run() == RESPONSE_GO )
    {
        AppWindow::p->panel_diary->update_entry_list();
        AppWindow::p->panel_extra->populate();
        update_combobox_chapter_ctg();
    }

    m_dialog_import->hide();
}

void
DiaryView::start_dialog_export()
{
    AppWindow::p->panel_main->hide_popover();

    if( m_ptr2elem->is_encrypted() ?
            DialogPassword::launch( DialogPassword::OT_AUTHENTICATE,
                                    m_ptr2elem ) == RESPONSE_GO : true )
    {
        if( m_dialog_export == nullptr )
            Lifeograph::get_builder2()->get_widget_derived( "D_export",
                                                            m_dialog_export );

        m_dialog_export->run();
        m_dialog_export->hide();
    }
}

void
DiaryView::handle_language_changed()
{
    if( Lifeograph::s_internaloperation )
        return;
    std::string lang( m_CB_spellcheck->get_active_text() );
    Diary::d->set_lang( lang == _( STRING::OFF ) ? "" : lang );
}

void
DiaryView::handle_startup_type_changed()
{
    if( ! Lifeograph::s_internaloperation )
    {
        Gtk::TreeRow row( * m_CB_startup_type->get_active() );
        Diary::d->m_startup_elem_id = row[ colrec_startup_type.type ];
    }
    bool flag_show_fixed_item( Diary::d->m_startup_elem_id >= DEID_DIARY );
    m_L_startup_elem->set_visible( flag_show_fixed_item );
    m_L_startup_elem_drop->set_visible( flag_show_fixed_item );
    if( flag_show_fixed_item )
       update_startup_elem();
}

void
DiaryView::update_startup_elem()
{
    m_L_startup_elem_drop->set_markup(
            Glib::ustring::compose( "<a href=\"#\" title=\"%2\">%1</a>",
                                    Diary::d->get_startup_elem()->get_list_str(),
                                    _( "Drop items here to change" ) ) ); // tooltip
}

bool
DiaryView::handle_startup_elem_drag_motion( const Glib::RefPtr<Gdk::DragContext>& context,
                                            int x, int y, guint time )
{
    if( Lifeograph::s_flag_dragging )
        if( Lifeograph::s_elem_dragged )
            if( Lifeograph::s_elem_dragged->get_type() == DiaryElement::ET_DIARY ||
                Lifeograph::s_elem_dragged->get_type() == DiaryElement::ET_ENTRY )
            {
                context->drag_status( Gdk::ACTION_COPY, time );
                return true;
            }

    m_L_startup_elem_drop->drag_unhighlight();  // is called many times unnecessarily :(
    context->drag_refuse( time );
    return false;
}

bool
DiaryView::handle_startup_elem_dropped(
                const Glib::RefPtr< Gdk::DragContext >& context,
                int x, int y,
                guint time )
{
    bool retval( false );

    context->drag_finish( true, false, time );

    if( Lifeograph::s_flag_dragging )
    {
        if( Lifeograph::s_elem_dragged != NULL )
        {
            switch( Lifeograph::s_elem_dragged->get_type() )
            {
                case DiaryElement::ET_ENTRY:
                case DiaryElement::ET_DIARY:
                    Diary::d->m_startup_elem_id = Lifeograph::s_elem_dragged->get_id();
                    update_startup_elem();
                    retval = true;
                    break;
                default:
                    break;
            }
        }
    }

    return retval;
}

bool
DiaryView::go_to_startup_elem( const Glib::ustring& )
{
    DiaryElement *element( Diary::d->get_startup_elem() );
    if( element != Diary::d )
        element->show();

    return true;
}

void
DiaryView::update_combobox_chapter_ctg()
{
    m_liststore_chapter_ctg->clear();

    Gtk::TreeRow row;

    for( auto& kv_chapter_ctg : m_ptr2elem->m_chapter_categories )
    {
        row = * m_liststore_chapter_ctg->append();

        CategoryChapters *category = kv_chapter_ctg.second;

        row[ colrec_chapter_ctg.name ] = category->get_name();
        row[ colrec_chapter_ctg.ptr ] = category;
        row[ colrec_chapter_ctg.type ] = CCCT_CATEGORY;

        // setting current item:
        if( m_ptr2elem->m_ptr2chapter_ctg_cur == category )
            m_CB_chapter_ctg->set_active( row );
    }

    m_B_chapter_ctg_rename->set_visible( false );
    if( ! Diary::d->is_read_only() )
    {
        m_B_chapter_ctg_dismiss->set_visible( true );
        m_B_chapter_ctg_dismiss->set_sensitive(
                m_ptr2elem->m_chapter_categories.size() > 1 );
    }
    else
    {
        m_B_chapter_ctg_dismiss->set_visible( false );
    }

    // separator 2:
    if( m_ptr2elem->m_chapter_categories.size() > 0 )
    {
        row = * m_liststore_chapter_ctg->append();
        row[ colrec_chapter_ctg.type ] = CCCT_SEPARATOR;
    }

    row = * m_liststore_chapter_ctg->append();

    row[ colrec_chapter_ctg.name ] = _( "Create New..." );
    row[ colrec_chapter_ctg.ptr ] = NULL;
    row[ colrec_chapter_ctg.type ] = CCCT_NEW;
}

void
DiaryView::handle_cur_chapter_ctg_changed()
{
    if( Lifeograph::s_internaloperation ) return;

    Gtk::TreeIter iter = m_CB_chapter_ctg->get_active();
    bool flag_renamed = ! ( iter ), flag_existing_ctg( false );

    if( flag_renamed )
    {
        Glib::ustring name = m_CB_chapter_ctg->get_entry_text();
        bool flag_name_usable = m_ptr2elem->m_chapter_categories.count( name ) < 1;
        m_B_chapter_ctg_rename->set_sensitive( flag_name_usable );
    }
    else
    {
        Gtk::TreeRow row = *iter;
        switch( row[ colrec_chapter_ctg.type ] )
        {
            case CCCT_NEW: // create new
                m_ptr2elem->set_current_chapter_ctg( m_ptr2elem->create_chapter_ctg() );
                update_combobox_chapter_ctg();
                flag_existing_ctg = true;
                break;
            case CCCT_CATEGORY:
                flag_existing_ctg = true;
                m_ptr2elem->set_current_chapter_ctg( row[ colrec_chapter_ctg.ptr ] );
                AppWindow::p->panel_diary->update_entry_list();
                break;
            default:	// for separators
                break;
        }

        m_B_chapter_ctg_dismiss->set_sensitive( flag_existing_ctg );
    }

    if( ! Diary::d->is_read_only() )
    {
        m_B_chapter_ctg_rename->set_visible( flag_renamed );
        m_B_chapter_ctg_dismiss->set_visible( ! flag_renamed );
    }

    update_points();
}

void
DiaryView::rename_cur_chapter_ctg()
{
    Glib::ustring name = m_CB_chapter_ctg->get_entry_text();
    if( name == m_ptr2elem->m_ptr2chapter_ctg_cur->get_name() )
        return;
    m_ptr2elem->rename_chapter_ctg( m_ptr2elem->m_ptr2chapter_ctg_cur, name );
    update_combobox_chapter_ctg();
}

void
DiaryView::dismiss_cur_chapter_ctg()
{
    if( ! AppWindow::p->confirm_dismiss_element( m_ptr2elem->m_ptr2chapter_ctg_cur ) )
        return;

    Diary::d->show();

    m_ptr2elem->dismiss_chapter_ctg( m_ptr2elem->m_ptr2chapter_ctg_cur );
    update_combobox_chapter_ctg();
}

bool
DiaryView::is_row_separator( const Glib::RefPtr< Gtk::TreeModel >& model,
                             const Gtk::TreeIter iter )
{
    Gtk::TreeRow row = * iter;
    return ( row[ colrec_chapter_ctg.type ] == CCCT_SEPARATOR );
}
