/*
 * rofi
 *
 * MIT/X11 License
 * Copyright © 2013-2020 Qball Cow <qball@gmpclient.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include <config.h>
#include <glib.h>
#include <widgets/widget.h>
#include <widgets/textbox.h>
#include <widgets/scrollbar.h>
#include <widgets/icon.h>
#include <widgets/box.h>
#include <widgets/listview.h>

#include "settings.h"
#include "theme.h"

#include "timings.h"

/** Default spacing between the elements in the listview. */
#define DEFAULT_SPACING    2

/**
 * Orientation of the listview
 */
/** Vertical (classical) list */
#define  LISTVIEW    ROFI_ORIENTATION_VERTICAL
/** Horizontal list. (barview) */
#define BARVIEW      ROFI_ORIENTATION_HORIZONTAL

/**
 * The moving direction of the selection, this (in barview) affects the scrolling.
 */
typedef enum
{
    LEFT_TO_RIGHT = 0,
    RIGHT_TO_LEFT = 1
} MoveDirection;

typedef struct
{
    box     *box;
    textbox *textbox;
    textbox *index;
    icon    *icon;
} _listview_row;

struct _listview
{
    widget          widget;

    RofiOrientation type;

    // RChanged
    // Text needs to be repainted.
    unsigned int                rchanged;
    // Administration

    unsigned int                cur_page;
    unsigned int                last_offset;
    unsigned int                selected;

    unsigned int                element_height;
    unsigned int                max_rows;
    unsigned int                max_elements;

    //
    gboolean                    fixed_columns;
    unsigned int                cur_columns;
    unsigned int                req_elements;
    unsigned int                cur_elements;

    RofiDistance                spacing;
    unsigned int                menu_lines;
    unsigned int                max_displayed_lines;
    unsigned int                menu_columns;
    unsigned int                fixed_num_lines;
    unsigned int                dynamic;
    unsigned int                eh;
    unsigned int                reverse;
    gboolean                    cycle;
    gboolean                    multi_select;

    ScrollType                  scroll_type;

    _listview_row               * boxes;
    scrollbar                   *scrollbar;

    listview_update_callback    callback;
    void                        *udata;

    gboolean                    scrollbar_scroll;

    xcb_timestamp_t             last_click;
    listview_mouse_activated_cb mouse_activated;
    void                        *mouse_activated_data;

    char                        *listview_name;

    PangoEllipsizeMode          emode;
    /** Barview */
    struct
    {
        MoveDirection direction;
        unsigned int  cur_visible;
    } barview;
};
/**
 * Names used for theming the elements in the listview.
 * Each row can have 3 modes,  normal, selected and alternate.
 * Each row can have 3 states, normal, urgent and active.
 */
const char *const listview_theme_prop_names[][3] = {
    /** Normal row */
    { "normal.normal", "selected.normal", "alternate.normal" },
    /** Urgent row */
    { "normal.urgent", "selected.urgent", "alternate.urgent" },
    /** Active row */
    { "normal.active", "selected.active", "alternate.active" },
};

static void listview_set_state ( _listview_row r, TextBoxFontType tbft )
{
    widget          *w = WIDGET ( r.box );
    TextBoxFontType t  = tbft & STATE_MASK;
    if ( w == NULL ) {
        return;
    }
    // ACTIVE has priority over URGENT if both set.
    if ( t == ( URGENT | ACTIVE ) ) {
        t = ACTIVE;
    }
    switch ( ( tbft & FMOD_MASK ) )
    {
    case HIGHLIGHT:
        widget_set_state ( w, listview_theme_prop_names[t][1] );
        break;
    case ALT:
        widget_set_state ( w, listview_theme_prop_names[t][2] );
        break;
    default:
        widget_set_state ( w, listview_theme_prop_names[t][0] );
        break;
    }
}
static void listview_add_widget ( listview *lv, _listview_row *row, widget *wid, const char *label )
{
    TextboxFlags flags = ( lv->multi_select ) ? TB_INDICATOR : 0;
    if ( strcasecmp ( label, "element-icon" ) == 0 ) {
        if ( config.show_icons ) {
            row->icon = icon_create ( WIDGET ( wid ), "element-icon" );
            box_add ( (box *) wid, WIDGET ( row->icon ), FALSE );
        }
    }
    else if ( strcasecmp ( label, "element-text" ) == 0 ) {
        row->textbox = textbox_create ( WIDGET ( wid ), WIDGET_TYPE_TEXTBOX_TEXT, "element-text", TB_AUTOHEIGHT | flags, NORMAL, "DDD", 0, 0 );
        box_add ( (box *) wid, WIDGET ( row->textbox ), TRUE );
    }
    else if ( strcasecmp ( label, "element-index" ) == 0 ) {
        row->index = textbox_create ( WIDGET ( wid ), WIDGET_TYPE_TEXTBOX_TEXT, "element-text", TB_AUTOHEIGHT, NORMAL, " ", 0, 0 );
        box_add ( (box *) wid, WIDGET ( row->index ), FALSE );
    }
    else {
        widget *wid2 = (widget *) box_create ( wid, label, ROFI_ORIENTATION_VERTICAL );
        box_add ( (box *) wid, WIDGET ( wid2 ), TRUE );
        GList  *list = rofi_theme_get_list ( WIDGET ( wid2 ), "children", "" );
        for ( GList *iter = g_list_first ( list ); iter != NULL; iter = g_list_next ( iter ) ) {
            listview_add_widget ( lv, row, wid2, (const char *) iter->data );
        }
    }
}

static void listview_create_row ( listview *lv, _listview_row *row )
{
    row->box = box_create ( WIDGET ( lv ), "element", ROFI_ORIENTATION_HORIZONTAL );
    widget_set_type ( WIDGET ( row->box ), WIDGET_TYPE_LISTVIEW_ELEMENT );
    GList *list = rofi_theme_get_list ( WIDGET ( row->box ), "children", "element-icon,element-text" );

    row->textbox = NULL;
    row->icon    = NULL;
    row->index   = NULL;

    for ( GList *iter = g_list_first ( list ); iter != NULL; iter = g_list_next ( iter ) ) {
        listview_add_widget ( lv, row, WIDGET ( row->box ), (const char *) iter->data );
    }
    g_list_free_full ( list, g_free );
}

static int listview_get_desired_height ( widget *wid );

static void listview_free ( widget *wid )
{
    listview *lv = (listview *) wid;
    for ( unsigned int i = 0; i < lv->cur_elements; i++ ) {
        widget_free ( WIDGET ( lv->boxes [i].box ) );
    }
    g_free ( lv->boxes );

    g_free ( lv->listview_name );
    widget_free ( WIDGET ( lv->scrollbar ) );
    g_free ( lv );
}
static unsigned int scroll_per_page_barview ( listview *lv )
{
    unsigned int offset = lv->last_offset;

    // selected row is always visible.
    // If selected is visible do not scroll.
    if ( lv->selected < lv->last_offset ) {
        offset       = lv->selected;
        lv->rchanged = TRUE;
    }
    else if ( lv->selected >= ( lv->last_offset + lv->barview.cur_visible ) ) {
        offset       = lv->selected;
        lv->rchanged = TRUE;
    }
    return offset;
}
static unsigned int scroll_per_page ( listview * lv )
{
    int offset = 0;

    // selected row is always visible.
    // If selected is visible do not scroll.
    if ( ( ( lv->selected - ( lv->last_offset ) ) < ( lv->max_elements ) ) && ( lv->selected >= ( lv->last_offset ) ) ) {
        offset = lv->last_offset;
    }
    else{
        // Do paginating
        unsigned int page = ( lv->max_elements > 0 ) ? ( lv->selected / lv->max_elements ) : 0;
        offset = page * lv->max_elements;
        if ( page != lv->cur_page ) {
            lv->cur_page = page;
            lv->rchanged = TRUE;
        }
        // Set the position
        //scrollbar_set_handle ( lv->scrollbar, page * lv->max_elements );
    }
    return offset;
}

static unsigned int scroll_continious ( listview *lv )
{
    unsigned int middle = ( lv->max_rows - ( ( lv->max_rows & 1 ) == 0 ) ) / 2;
    unsigned int offset = 0;
    if ( lv->selected > middle ) {
        if ( lv->selected < ( lv->req_elements - ( lv->max_rows - middle ) ) ) {
            offset = lv->selected - middle;
        }
        // Don't go below zero.
        else if ( lv->req_elements > lv->max_rows ) {
            offset = lv->req_elements - lv->max_rows;
        }
    }
    if ( offset != lv->cur_page ) {
        //scrollbar_set_handle ( lv->scrollbar, offset );
        lv->cur_page = offset;
        lv->rchanged = TRUE;
    }
    return offset;
}

static void update_element ( listview *lv, unsigned int tb, unsigned int index, gboolean full )
{
    // Select drawing mode
    TextBoxFontType type = ( index & 1 ) == 0 ? NORMAL : ALT;
    type = ( index ) == lv->selected ? HIGHLIGHT : type;

    if ( lv->boxes[tb].index ) {
        if ( index < 10 ) {
            char str[2] = {
                ( ( index + 1 ) % 10 ) + '0',
                '\0'
            };
            textbox_text ( lv->boxes[tb].index, str );
        }
        else {
            textbox_text ( lv->boxes[tb].index, " " );
        }
    }
    if ( lv->callback ) {
        lv->callback ( lv->boxes[tb].textbox, lv->boxes[tb].icon, index, lv->udata, &type, full );
        listview_set_state ( lv->boxes[tb], type );
    }
}

static void barview_draw ( widget *wid, cairo_t *draw )
{
    unsigned int offset = 0;
    listview     *lv    = (listview *) wid;
    offset          = scroll_per_page_barview ( lv );
    lv->last_offset = offset;
    int spacing_hori = distance_get_pixel ( lv->spacing, ROFI_ORIENTATION_HORIZONTAL );

    int left_offset  = widget_padding_get_left ( wid );
    int right_offset = lv->widget.w - widget_padding_get_right ( wid );
    int top_offset   = widget_padding_get_top ( wid );
    if ( lv->cur_elements > 0 ) {
        // Set new x/y position.
        unsigned int max = MIN ( lv->cur_elements, lv->req_elements - offset );
        if ( lv->rchanged ) {
            int first = TRUE;
            int width = lv->widget.w;
            lv->barview.cur_visible = 0;
            width                  -= widget_padding_get_padding_width ( wid );
            if ( lv->barview.direction == LEFT_TO_RIGHT ) {
                for ( unsigned int i = 0; i < max && width > 0; i++ ) {
                    update_element ( lv, i, i + offset, TRUE );
                    int twidth = widget_get_desired_width ( WIDGET ( lv->boxes[i].box ) );
                    if ( twidth >= width ) {
                        if ( !first ) {
                            break;
                        }
                        twidth = width;
                    }
                    widget_move ( WIDGET ( lv->boxes[i].box ), left_offset, top_offset );
                    widget_resize ( WIDGET ( lv->boxes[i].box ), twidth, lv->element_height );

                    widget_draw ( WIDGET ( lv->boxes[i].box ), draw );
                    width       -= twidth + spacing_hori;
                    left_offset += twidth + spacing_hori;
                    first        = FALSE;
                    lv->barview.cur_visible++;
                }
            }
            else {
                for ( unsigned int i = 0; i < lv->cur_elements && width > 0 && i <= offset; i++ ) {
                    update_element ( lv, i, offset - i, TRUE );
                    int twidth = widget_get_desired_width ( WIDGET ( lv->boxes[i].box ) );
                    if ( twidth >= width ) {
                        if ( !first ) {
                            break;
                        }
                        twidth = width;
                    }
                    right_offset -= twidth;
                    widget_move ( WIDGET ( lv->boxes[i].box ), right_offset, top_offset );
                    widget_resize ( WIDGET ( lv->boxes[i].box ), twidth, lv->element_height );

                    widget_draw ( WIDGET ( lv->boxes[i].box ), draw );
                    width        -= twidth + spacing_hori;
                    right_offset -= spacing_hori;
                    first         = FALSE;
                    lv->barview.cur_visible++;
                }
                offset         -= lv->barview.cur_visible - 1;
                lv->last_offset = offset;
                for  ( unsigned int i = 0; i < ( lv->barview.cur_visible / 2 ); i++ ) {
                    _listview_row temp = lv->boxes[i];
                    int           sw   = lv->barview.cur_visible - i - 1;
                    lv->boxes[i]  = lv->boxes[sw];
                    lv->boxes[sw] = temp;
                }
            }
            lv->rchanged = FALSE;
        }
        else {
            for ( unsigned int i = 0; i < lv->barview.cur_visible; i++ ) {
                update_element ( lv, i, i + offset, TRUE );
                widget_draw ( WIDGET ( lv->boxes[i].box ), draw );
            }
        }
    }
}

static void listview_draw ( widget *wid, cairo_t *draw )
{
    unsigned int offset = 0;
    listview     *lv    = (listview *) wid;
    if ( lv->scroll_type == LISTVIEW_SCROLL_CONTINIOUS ) {
        offset = scroll_continious ( lv );
    }
    else {
        offset = scroll_per_page ( lv );
    }
    // Set these all together to make sure they update consistently.
    scrollbar_set_max_value ( lv->scrollbar, lv->req_elements );
    scrollbar_set_handle_length ( lv->scrollbar, lv->cur_columns * lv->max_rows );
    if ( lv->reverse ) {
        scrollbar_set_handle ( lv->scrollbar, lv->req_elements - lv->selected - 1 );
    }
    else {
        scrollbar_set_handle ( lv->scrollbar, lv->selected  );
    }
    lv->last_offset = offset;
    int spacing_vert = distance_get_pixel ( lv->spacing, ROFI_ORIENTATION_VERTICAL );
    int spacing_hori = distance_get_pixel ( lv->spacing, ROFI_ORIENTATION_HORIZONTAL );

    int left_offset = widget_padding_get_left ( wid );
    int top_offset  = widget_padding_get_top ( wid );
    /*
       if ( lv->scrollbar->widget.index == 0 ) {
        left_offset += spacing_hori + lv->scrollbar->widget.w;
       }
     */
    if ( lv->cur_elements > 0 && lv->max_rows > 0 ) {
        // Set new x/y position.
        unsigned int max = MIN ( lv->cur_elements, lv->req_elements - offset );
        if ( lv->rchanged ) {
            unsigned int width = lv->widget.w;
            width -= widget_padding_get_padding_width ( wid );
            if ( widget_enabled ( WIDGET ( lv->scrollbar ) ) ) {
                width -= spacing_hori;
                width -= widget_get_width ( WIDGET ( lv->scrollbar ) );
            }
            unsigned int element_width = ( width - spacing_hori * ( lv->cur_columns - 1 ) ) / lv->cur_columns;

            int          d = width - ( element_width + spacing_hori ) * ( lv->cur_columns - 1 ) - element_width;
            if ( lv->cur_columns > 1 ) {
                int diff = d / ( lv->cur_columns - 1 );
                if ( diff >= 1 ) {
                    spacing_hori += 1;
                    d            -= lv->cur_columns - 1;
                }
            }
            for ( unsigned int i = 0; i < max; i++ ) {
                unsigned int ex = left_offset + ( ( i ) / lv->max_rows ) * ( element_width + spacing_hori );

                if ( ( i ) / lv->max_rows == ( lv->cur_columns - 1 ) ) {
                    ex += d;
                }
                if ( lv->reverse ) {
                    unsigned int ey = wid->h - ( widget_padding_get_bottom ( wid ) + ( ( i ) % lv->max_rows ) * ( lv->element_height + spacing_vert ) ) - lv->element_height;
                    widget_move ( WIDGET ( lv->boxes[i].box ), ex, ey );
                    widget_resize ( WIDGET ( lv->boxes[i].box ), element_width, lv->element_height );
                }
                else {
                    unsigned int ey = top_offset + ( ( i ) % lv->max_rows ) * ( lv->element_height + spacing_vert );
                    widget_move ( WIDGET ( lv->boxes[i].box ), ex, ey );
                    widget_resize ( WIDGET ( lv->boxes[i].box ), element_width, lv->element_height );
                }

                update_element ( lv, i, i + offset, TRUE );
                widget_draw ( WIDGET ( lv->boxes[i].box ), draw );
            }
            lv->rchanged = FALSE;
        }
        else {
            for ( unsigned int i = 0; i < max; i++ ) {
                update_element ( lv, i, i + offset, TRUE );
                widget_draw ( WIDGET ( lv->boxes[i].box ), draw );
            }
        }
    }
    widget_draw ( WIDGET ( lv->scrollbar ), draw );
}
static WidgetTriggerActionResult listview_element_trigger_action ( widget *wid, MouseBindingListviewElementAction action, gint x, gint y, void *user_data );

static void _listview_draw ( widget *wid, cairo_t *draw )
{
    listview *lv = (listview *) wid;
    if ( lv->type == LISTVIEW ) {
        listview_draw ( wid, draw );
    }
    else {
        barview_draw ( wid, draw );
    }
}
/**
 * State names used for theming.
 */
static void listview_recompute_elements ( listview *lv )
{
    unsigned int newne = 0;
    if ( lv->max_rows == 0 ) {
        return;
    }
    if ( !( lv->fixed_columns ) && lv->req_elements < lv->max_elements ) {
        newne           = lv->req_elements;
        lv->cur_columns = ( lv->req_elements + ( lv->max_rows - 1 ) ) / lv->max_rows;
    }
    else {
        newne           = MIN ( lv->req_elements, lv->max_elements );
        lv->cur_columns = lv->menu_columns;
    }
    for ( unsigned int i = newne; i < lv->cur_elements; i++ ) {
        widget_free ( WIDGET ( lv->boxes[i].box ) );
    }
    lv->boxes = g_realloc ( lv->boxes, newne * sizeof ( _listview_row ) );
    if ( newne > 0   ) {
        for ( unsigned int i = lv->cur_elements; i < newne; i++ ) {
            listview_create_row ( lv, &( lv->boxes[i] ) );
            widget_set_trigger_action_handler ( WIDGET ( lv->boxes[i].box ), listview_element_trigger_action, lv );
            listview_set_state ( lv->boxes[i], NORMAL );
        }
    }
    lv->rchanged     = TRUE;
    lv->cur_elements = newne;
}

void listview_set_num_elements ( listview *lv, unsigned int rows )
{
    if ( lv == NULL ) {
        return;
    }
    TICK_N ( __FUNCTION__ );
    lv->req_elements = rows;
    listview_set_selected ( lv, lv->selected );
    TICK_N ( "Set selected" );
    listview_recompute_elements ( lv );
    TICK_N ( "recompute elements" );
    widget_queue_redraw ( WIDGET ( lv ) );
    TICK_N ( "queue redraw" );
}

unsigned int listview_get_selected ( listview *lv )
{
    if ( lv != NULL ) {
        return lv->selected;
    }
    return 0;
}

void listview_set_selected ( listview *lv, unsigned int selected )
{
    if ( lv && lv->req_elements > 0 ) {
        lv->selected          = MIN ( selected, lv->req_elements - 1 );
        lv->barview.direction = LEFT_TO_RIGHT;
        widget_queue_redraw ( WIDGET ( lv ) );
    }
}

static void listview_resize ( widget *wid, short w, short h )
{
    listview *lv = (listview *) wid;
    lv->widget.w = MAX ( 0, w );
    lv->widget.h = MAX ( 0, h );
    int height       = lv->widget.h - widget_padding_get_padding_height ( WIDGET ( lv ) );
    int spacing_vert = distance_get_pixel ( lv->spacing, ROFI_ORIENTATION_VERTICAL );
    lv->max_rows     = ( spacing_vert + height ) / ( lv->element_height + spacing_vert );
    lv->max_elements = lv->max_rows * lv->menu_columns;

    widget_move ( WIDGET ( lv->scrollbar ),
                  lv->widget.w - widget_padding_get_right ( WIDGET ( lv ) ) - widget_get_width ( WIDGET ( lv->scrollbar ) ),
                  widget_padding_get_top ( WIDGET ( lv ) ) );

    widget_resize (  WIDGET ( lv->scrollbar ), widget_get_width ( WIDGET ( lv->scrollbar ) ), height );

    if ( lv->type == BARVIEW ) {
        lv->max_elements = lv->menu_lines;
    }

    listview_recompute_elements ( lv );
    widget_queue_redraw ( wid );
}

static widget *listview_find_mouse_target ( widget *wid, WidgetType type, gint x, gint y )
{
    widget   *target = NULL;
    gint     rx, ry;
    listview *lv = (listview *) wid;
    if ( widget_enabled ( WIDGET ( lv->scrollbar ) ) && widget_intersect ( WIDGET ( lv->scrollbar ), x, y ) ) {
        rx     = x - widget_get_x_pos ( WIDGET ( lv->scrollbar ) );
        ry     = y - widget_get_y_pos ( WIDGET ( lv->scrollbar ) );
        target = widget_find_mouse_target ( WIDGET ( lv->scrollbar ), type, rx, ry );
    }

    unsigned int max = MIN ( lv->cur_elements, lv->req_elements - lv->last_offset );
    unsigned int i;
    for ( i = 0; i < max && target == NULL; i++ ) {
        widget *w = WIDGET ( lv->boxes[i].box );
        if ( widget_intersect ( w, x, y ) ) {
            rx     = x - widget_get_x_pos ( w );
            ry     = y - widget_get_y_pos ( w );
            target = widget_find_mouse_target ( w, type, rx, ry );
        }
    }

    return target;
}

static WidgetTriggerActionResult listview_trigger_action ( widget *wid, MouseBindingListviewAction action, G_GNUC_UNUSED gint x, G_GNUC_UNUSED gint y, G_GNUC_UNUSED void *user_data )
{
    listview *lv = (listview *) wid;
    switch ( action )
    {
    case SCROLL_LEFT:
        listview_nav_left ( lv );
        break;
    case SCROLL_RIGHT:
        listview_nav_right ( lv );
        break;
    case SCROLL_DOWN:
        listview_nav_down ( lv );
        break;
    case SCROLL_UP:
        listview_nav_up ( lv );
        break;
    }
    return WIDGET_TRIGGER_ACTION_RESULT_HANDLED;
}

static WidgetTriggerActionResult listview_element_trigger_action ( widget *wid, MouseBindingListviewElementAction action, G_GNUC_UNUSED gint x, G_GNUC_UNUSED gint y, void *user_data )
{
    listview     *lv = (listview *) user_data;
    unsigned int max = MIN ( lv->cur_elements, lv->req_elements - lv->last_offset );
    unsigned int i;
    for ( i = 0; i < max && WIDGET ( lv->boxes[i].box ) != wid; i++ ) {
    }
    if ( i == max ) {
        return WIDGET_TRIGGER_ACTION_RESULT_IGNORED;
    }

    gboolean custom = FALSE;
    switch ( action )
    {
    case SELECT_HOVERED_ENTRY:
        listview_set_selected ( lv, lv->last_offset + i );
        break;
    case ACCEPT_HOVERED_CUSTOM:
        custom = TRUE;
    /* FALLTHRU */
    case ACCEPT_HOVERED_ENTRY:
        listview_set_selected ( lv, lv->last_offset + i );
        lv->mouse_activated ( lv, custom, lv->mouse_activated_data );
        break;
    }
    return WIDGET_TRIGGER_ACTION_RESULT_HANDLED;
}

listview *listview_create ( widget *parent, const char *name, listview_update_callback cb, void *udata, unsigned int eh, gboolean reverse )
{
    listview *lv = g_malloc0 ( sizeof ( listview ) );
    widget_init ( WIDGET ( lv ), parent, WIDGET_TYPE_LISTVIEW, name );
    lv->listview_name             = g_strdup ( name );
    lv->widget.free               = listview_free;
    lv->widget.resize             = listview_resize;
    lv->widget.draw               = _listview_draw;
    lv->widget.find_mouse_target  = listview_find_mouse_target;
    lv->widget.trigger_action     = listview_trigger_action;
    lv->widget.get_desired_height = listview_get_desired_height;
    lv->eh                        = eh;

    lv->emode     = PANGO_ELLIPSIZE_END;
    lv->scrollbar = scrollbar_create ( WIDGET ( lv ), "scrollbar" );
    // Calculate height of an element.
    //
    _listview_row row;
    listview_create_row ( lv, &row );
    // FIXME: hack to scale hight correctly.
    if ( lv->eh > 1 && row.textbox ) {
        char buff[lv->eh * 2 + 1];
        memset ( buff, '\0', lv->eh * 2 + 1 );
        for ( unsigned int i = 0; i < ( lv->eh - 1 ); i++ ) {
            buff[i * 2] = 'a'; buff[i * 2 + 1] = '\n';
        }
        ;
        textbox_text ( row.textbox, buff );
    }
    lv->element_height = widget_get_desired_height ( WIDGET ( row.box ) );
    widget_free ( WIDGET ( row.box ) );

    lv->callback = cb;
    lv->udata    = udata;

    // Some settings.
    lv->spacing         = rofi_theme_get_distance ( WIDGET ( lv ), "spacing", DEFAULT_SPACING );
    lv->menu_columns    = rofi_theme_get_integer  ( WIDGET ( lv ), "columns", config.menu_columns );
    lv->fixed_num_lines = rofi_theme_get_boolean  ( WIDGET ( lv ), "fixed-height", config.fixed_num_lines );
    lv->dynamic         = rofi_theme_get_boolean  ( WIDGET ( lv ), "dynamic", TRUE );
    lv->reverse         = rofi_theme_get_boolean  ( WIDGET ( lv ), "reverse", reverse );
    lv->cycle           = rofi_theme_get_boolean ( WIDGET ( lv ), "cycle", config.cycle );
    lv->fixed_columns   = rofi_theme_get_boolean ( WIDGET ( lv ), "fixed-columns", FALSE );

    lv->type = rofi_theme_get_orientation ( WIDGET ( lv ), "layout", ROFI_ORIENTATION_VERTICAL );
    if ( lv->type == LISTVIEW ) {
        listview_set_show_scrollbar ( lv, rofi_theme_get_boolean ( WIDGET ( lv ), "scrollbar", FALSE ) );
    }
    else {
        listview_set_show_scrollbar ( lv, FALSE );
    }
    return lv;
}

/**
 * Navigation commands.
 */

static void listview_nav_up_int ( listview *lv )
{
    if ( lv == NULL ) {
        return;
    }
    if ( lv->req_elements == 0 || ( lv->selected == 0 && !lv->cycle ) ) {
        return;
    }
    if ( lv->selected == 0 ) {
        lv->selected = lv->req_elements;
    }
    lv->selected--;
    lv->barview.direction = RIGHT_TO_LEFT;
    widget_queue_redraw ( WIDGET ( lv ) );
}
static void listview_nav_down_int ( listview *lv )
{
    if ( lv == NULL ) {
        return;
    }
    if ( lv->req_elements == 0 || ( lv->selected == ( lv->req_elements - 1 ) && !lv->cycle ) ) {
        return;
    }
    lv->selected          = lv->selected < lv->req_elements - 1 ? MIN ( lv->req_elements - 1, lv->selected + 1 ) : 0;
    lv->barview.direction = LEFT_TO_RIGHT;
    widget_queue_redraw ( WIDGET ( lv ) );
}

void listview_nav_up ( listview *lv )
{
    if ( lv == NULL ) {
        return;
    }
    if ( lv->reverse ) {
        listview_nav_down_int ( lv );
    }
    else {
        listview_nav_up_int ( lv );
    }
}
void listview_nav_down ( listview *lv )
{
    if ( lv == NULL ) {
        return;
    }
    if ( lv->reverse ) {
        listview_nav_up_int ( lv );
    }
    else {
        listview_nav_down_int ( lv );
    }
}

void listview_nav_left ( listview *lv )
{
    if ( lv == NULL ) {
        return;
    }
    if ( lv->type == BARVIEW ) {
        listview_nav_up_int ( lv );
        return;
    }
    if ( lv->selected >= lv->max_rows ) {
        lv->selected -= lv->max_rows;
        widget_queue_redraw ( WIDGET ( lv ) );
    }
}
void listview_nav_right ( listview *lv )
{
    if ( lv == NULL ) {
        return;
    }
    if ( lv->type == BARVIEW ) {
        listview_nav_down_int ( lv );
        return;
    }
    if ( ( lv->selected + lv->max_rows ) < lv->req_elements ) {
        lv->selected += lv->max_rows;
        widget_queue_redraw ( WIDGET ( lv ) );
    }
    else if ( lv->selected < ( lv->req_elements - 1 ) ) {
        // We do not want to move to last item, UNLESS the last column is only
        // partially filled, then we still want to move column and select last entry.
        // First check the column we are currently in.
        int col = lv->selected / lv->max_rows;
        // Check total number of columns.
        int ncol = lv->req_elements / lv->max_rows;
        // If there is an extra column, move.
        if ( col != ncol ) {
            lv->selected = lv->req_elements - 1;
            widget_queue_redraw ( WIDGET ( lv ) );
        }
    }
}

static void listview_nav_page_prev_int ( listview *lv )
{
    if ( lv == NULL ) {
        return;
    }
    if ( lv->type == BARVIEW ) {
        if ( lv->last_offset == 0 ) {
            lv->selected = 0;
        }
        else {
            lv->selected = lv->last_offset - 1;
        }
        lv->barview.direction = RIGHT_TO_LEFT;
        widget_queue_redraw ( WIDGET ( lv ) );
        return;
    }

    if ( lv->selected < lv->max_elements ) {
        lv->selected = 0;
    }
    else{
        lv->selected -= ( lv->max_elements );
    }
    widget_queue_redraw ( WIDGET ( lv ) );
}
static void listview_nav_page_next_int ( listview *lv )
{
    if ( lv == NULL ) {
        return;
    }
    if ( lv->req_elements == 0 ) {
        return;
    }
    if ( lv->type == BARVIEW ) {
        unsigned int new = lv->last_offset + lv->barview.cur_visible;
        lv->selected          = MIN ( new, lv->req_elements - 1 );
        lv->barview.direction = LEFT_TO_RIGHT;

        widget_queue_redraw ( WIDGET ( lv ) );
        return;
    }
    lv->selected += ( lv->max_elements );
    if ( lv->selected >= lv->req_elements ) {
        lv->selected = lv->req_elements - 1;
    }
    widget_queue_redraw ( WIDGET ( lv ) );
}

void listview_nav_page_prev ( listview *lv )
{
    if ( lv == NULL ) {
        return;
    }
    if ( lv->reverse ) {
        listview_nav_page_next_int ( lv );
    }
    else {
        listview_nav_page_prev_int ( lv );
    }
}
void listview_nav_page_next ( listview *lv )
{
    if ( lv == NULL ) {
        return;
    }
    if ( lv->reverse ) {
        listview_nav_page_prev_int ( lv );
    }
    else {
        listview_nav_page_next_int ( lv );
    }
}

static int listview_get_desired_height ( widget *wid )
{
    listview *lv = (listview *) wid;
    if ( lv == NULL || lv->widget.enabled == FALSE ) {
        return 0;
    }
    int spacing = distance_get_pixel ( lv->spacing, ROFI_ORIENTATION_VERTICAL );
    int h       = lv->menu_lines;
    if ( !( lv->fixed_num_lines ) ) {
        if ( lv->dynamic ) {
            h = MIN ( lv->menu_lines, lv->req_elements );
        }
        else {
            h = MIN ( lv->menu_lines, lv->max_displayed_lines );
        }
    }
    if ( lv->type == BARVIEW ) {
        h = MIN ( h, 1 );
    }
    if ( h == 0 ) {
        if ( lv->dynamic && !lv->fixed_num_lines ) {
            // Hide widget fully.
            return 0;
        }
        return widget_padding_get_padding_height ( WIDGET ( lv ) );
    }
    int height = widget_padding_get_padding_height ( WIDGET ( lv ) );
    height += h * ( lv->element_height + spacing ) - spacing;
    return height;
}

void listview_set_show_scrollbar ( listview *lv, gboolean enabled )
{
    if ( lv ) {
        if ( enabled ) {
            widget_enable ( WIDGET ( lv->scrollbar ) );
        }
        else {
            widget_disable ( WIDGET ( lv->scrollbar ) );
        }
        listview_recompute_elements ( lv );
    }
}

void listview_set_scroll_type ( listview *lv, ScrollType type )
{
    if ( lv ) {
        lv->scroll_type = type;
    }
}

void listview_set_mouse_activated_cb ( listview *lv, listview_mouse_activated_cb cb, void *udata )
{
    if ( lv ) {
        lv->mouse_activated      = cb;
        lv->mouse_activated_data = udata;
    }
}
void listview_set_multi_select ( listview *lv, gboolean enable )
{
    if ( lv ) {
        lv->multi_select = enable;
    }
}
void listview_set_num_lines ( listview *lv, unsigned int num_lines )
{
    if ( lv ) {
        lv->menu_lines = num_lines;
    }
}

unsigned int listview_get_num_lines ( listview *lv )
{
    if ( lv ) {
        return lv->menu_lines;
    }
    return 0;
}
void listview_set_max_lines ( listview *lv, unsigned int max_lines )
{
    if ( lv ) {
        lv->max_displayed_lines = max_lines;
    }
}

gboolean listview_get_fixed_num_lines ( listview *lv )
{
    if ( lv ) {
        return lv->fixed_num_lines;
    }
    return FALSE;
}
void listview_set_fixed_num_lines ( listview *lv )
{
    if ( lv  ) {
        lv->fixed_num_lines = TRUE;
    }
}

void listview_set_ellipsize_start ( listview *lv )
{
    if ( lv ) {
        lv->emode = PANGO_ELLIPSIZE_START;
        for ( unsigned int i = 0; i < lv->cur_elements; i++ ) {
            textbox_set_ellipsize ( lv->boxes[i].textbox, lv->emode );
        }
    }
}

void listview_toggle_ellipsizing ( listview *lv )
{
    if ( lv ) {
        PangoEllipsizeMode mode = lv->emode;
        if ( mode == PANGO_ELLIPSIZE_START ) {
            mode = PANGO_ELLIPSIZE_MIDDLE;
        }
        else if ( mode == PANGO_ELLIPSIZE_MIDDLE ) {
            mode = PANGO_ELLIPSIZE_END;
        }
        else if ( mode == PANGO_ELLIPSIZE_END ) {
            mode = PANGO_ELLIPSIZE_START;
        }
        lv->emode = mode;
        for ( unsigned int i = 0; i < lv->cur_elements; i++ ) {
            textbox_set_ellipsize ( lv->boxes[i].textbox, mode );
        }
    }
}