rofi/source/widgets/listview.c

993 lines
32 KiB
C

/*
* 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 = textbox_get_desired_width ( WIDGET ( lv->boxes[i].textbox ) );
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 = textbox_get_desired_width ( WIDGET ( lv->boxes[i].textbox ) );
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 );
}
}
}