summaryrefslogtreecommitdiffstats
path: root/source/view.c
diff options
context:
space:
mode:
authorDave Davenport <qball@gmpclient.org>2016-02-06 14:27:36 +0100
committerDave Davenport <qball@gmpclient.org>2016-02-06 14:27:36 +0100
commitadfc83f07d17b6eed67a3e37ca3ced75bb6ec96d (patch)
treec18281792c3c984ca8daacbc2306f0b1a503e323 /source/view.c
parent73169af793c59ee1847325d7faa3f3cf6b4c08c0 (diff)
Restructuring, my biggest joy.
Diffstat (limited to 'source/view.c')
-rw-r--r--source/view.c1504
1 files changed, 1500 insertions, 4 deletions
diff --git a/source/view.c b/source/view.c
index 1608c3d7..a7a16468 100644
--- a/source/view.c
+++ b/source/view.c
@@ -64,13 +64,157 @@
#include "dialogs/dialogs.h"
#include "rofi.h"
-extern RofiViewState *current_active_menu;
-extern Display *display;
-extern unsigned int num_modi;
-extern Window main_window;
+extern RofiViewState *current_active_menu;
+extern Display *display;
+extern unsigned int num_modi;
+Window main_window = None;
+extern SnDisplay *sndisplay;
+extern SnLauncheeContext *sncontext;
+extern GThreadPool *tpool;
+extern unsigned int curr_switcher;
+extern Mode **modi;
+extern unsigned int num_modi;
+cairo_surface_t *surface = NULL;
+cairo_surface_t *fake_bg = NULL;
+cairo_t *draw = NULL;
+XIM xim;
+XIC xic;
+Colormap map = None;
+XVisualInfo vinfo;
+extern unsigned int normal_window_mode;
+
+typedef enum _MainLoopEvent
+{
+ ML_XEVENT,
+ ML_TIMEOUT
+} MainLoopEvent;
#include "view.h"
+#include "view-internal.h"
+
+static char * get_matching_state ( void )
+{
+ if ( config.case_sensitive ) {
+ if ( config.levenshtein_sort ) {
+ return "±";
+ }
+ else {
+ return "-";
+ }
+ }
+ else{
+ if ( config.levenshtein_sort ) {
+ return "+";
+ }
+ }
+ return " ";
+}
+/**
+ * Levenshtein Sorting.
+ */
+static int lev_sort ( const void *p1, const void *p2, void *arg )
+{
+ const int *a = p1;
+ const int *b = p2;
+ int *distances = arg;
+
+ return distances[*a] - distances[*b];
+}
+
+/**
+ * Stores a screenshot of Rofi at that point in time.
+ */
+static void menu_capture_screenshot ( void )
+{
+ const char *outp = g_getenv ( "ROFI_PNG_OUTPUT" );
+ if ( surface == NULL ) {
+ // Nothing to store.
+ fprintf ( stderr, "There is no rofi surface to store\n" );
+ return;
+ }
+ const char *xdg_pict_dir = g_get_user_special_dir ( G_USER_DIRECTORY_PICTURES );
+ if ( outp == NULL && xdg_pict_dir == NULL ) {
+ fprintf ( stderr, "XDG user picture directory or ROFI_PNG_OUTPUT is not set. Cannot store screenshot.\n" );
+ return;
+ }
+ // Get current time.
+ GDateTime *now = g_date_time_new_now_local ();
+ // Format filename.
+ char *timestmp = g_date_time_format ( now, "rofi-%Y-%m-%d-%H%M" );
+ char *filename = g_strdup_printf ( "%s.png", timestmp );
+ // Build full path
+ char *fpath = NULL;
+ if ( outp == NULL ) {
+ int index = 0;
+ fpath = g_build_filename ( xdg_pict_dir, filename, NULL );
+ while ( g_file_test ( fpath, G_FILE_TEST_EXISTS ) && index < 99 ) {
+ g_free ( fpath );
+ g_free ( filename );
+ // Try the next index.
+ index++;
+ // Format filename.
+ filename = g_strdup_printf ( "%s-%d.png", timestmp, index );
+ // Build full path
+ fpath = g_build_filename ( xdg_pict_dir, filename, NULL );
+ }
+ }
+ else {
+ fpath = g_strdup ( outp );
+ }
+ fprintf ( stderr, color_green "Storing screenshot %s\n"color_reset, fpath );
+ cairo_status_t status = cairo_surface_write_to_png ( surface, fpath );
+ if ( status != CAIRO_STATUS_SUCCESS ) {
+ fprintf ( stderr, "Failed to produce screenshot '%s', got error: '%s'\n", filename,
+ cairo_status_to_string ( status ) );
+ }
+ g_free ( fpath );
+ g_free ( filename );
+ g_free ( timestmp );
+ g_date_time_unref ( now );
+}
+/**
+ * @param state the state of the View.
+ * @param mon the work area.
+ *
+ * Calculates the window poslition
+ */
+static void calculate_window_position ( RofiViewState *state )
+{
+ // Default location is center.
+ state->y = state->mon.y + ( state->mon.h - state->h ) / 2;
+ state->x = state->mon.x + ( state->mon.w - state->w ) / 2;
+ // Determine window location
+ switch ( config.location )
+ {
+ case WL_NORTH_WEST:
+ state->x = state->mon.x;
+ case WL_NORTH:
+ state->y = state->mon.y;
+ break;
+ case WL_NORTH_EAST:
+ state->y = state->mon.y;
+ case WL_EAST:
+ state->x = state->mon.x + state->mon.w - state->w;
+ break;
+ case WL_EAST_SOUTH:
+ state->x = state->mon.x + state->mon.w - state->w;
+ case WL_SOUTH:
+ state->y = state->mon.y + state->mon.h - state->h;
+ break;
+ case WL_SOUTH_WEST:
+ state->y = state->mon.y + state->mon.h - state->h;
+ case WL_WEST:
+ state->x = state->mon.x;
+ break;
+ case WL_CENTER:
+ default:
+ break;
+ }
+ // Apply offset.
+ state->x += config.x_offset;
+ state->y += config.y_offset;
+}
void rofi_view_queue_redraw ( void )
{
if ( current_active_menu ) {
@@ -186,6 +330,824 @@ RofiViewState * rofi_view_state_create ( void )
return g_malloc0 ( sizeof ( RofiViewState ) );
}
+typedef struct _thread_state
+{
+ RofiViewState *state;
+ char **tokens;
+ unsigned int start;
+ unsigned int stop;
+ unsigned int count;
+ GCond *cond;
+ GMutex *mutex;
+ unsigned int *acount;
+ void ( *callback )( struct _thread_state *t, gpointer data );
+}thread_state;
+
+static void filter_elements ( thread_state *t, G_GNUC_UNUSED gpointer user_data )
+{
+ // input changed
+ for ( unsigned int i = t->start; i < t->stop; i++ ) {
+ int match = mode_token_match ( t->state->sw, t->tokens,
+ t->state->lines_not_ascii[i],
+ config.case_sensitive,
+ i );
+ // If each token was matched, add it to list.
+ if ( match ) {
+ t->state->line_map[t->start + t->count] = i;
+ if ( config.levenshtein_sort ) {
+ // This is inefficient, need to fix it.
+ char * str = mode_get_completion ( t->state->sw, i );
+ t->state->distance[i] = levenshtein ( t->state->text->text, str );
+ g_free ( str );
+ }
+ t->count++;
+ }
+ }
+ g_mutex_lock ( t->mutex );
+ ( *( t->acount ) )--;
+ g_cond_signal ( t->cond );
+ g_mutex_unlock ( t->mutex );
+}
+static void check_is_ascii ( thread_state *t, G_GNUC_UNUSED gpointer user_data )
+{
+ for ( unsigned int i = t->start; i < t->stop; i++ ) {
+ t->state->lines_not_ascii[i] = mode_is_not_ascii ( t->state->sw, i );
+ }
+ g_mutex_lock ( t->mutex );
+ ( *( t->acount ) )--;
+ g_cond_signal ( t->cond );
+ g_mutex_unlock ( t->mutex );
+}
+
+static Window create_window ( Display *display )
+{
+ XSetWindowAttributes attr;
+ attr.colormap = map;
+ attr.border_pixel = 0;
+ attr.background_pixel = 0;
+
+ Window box = XCreateWindow ( display, DefaultRootWindow ( display ), 0, 0, 200, 100, 0, vinfo.depth, InputOutput,
+ vinfo.visual, CWColormap | CWBorderPixel | CWBackPixel, &attr );
+ XSelectInput (
+ display,
+ box,
+ KeyReleaseMask | KeyPressMask | ExposureMask | ButtonPressMask | StructureNotifyMask | FocusChangeMask |
+ Button1MotionMask );
+
+ surface = cairo_xlib_surface_create ( display, box, vinfo.visual, 200, 100 );
+ // Create a drawable.
+ draw = cairo_create ( surface );
+ cairo_set_operator ( draw, CAIRO_OPERATOR_SOURCE );
+
+ // Set up pango context.
+ cairo_font_options_t *fo = cairo_font_options_create ();
+ // Take font description from xlib surface
+ cairo_surface_get_font_options ( surface, fo );
+ PangoContext *p = pango_cairo_create_context ( draw );
+ // Set the font options from the xlib surface
+ pango_cairo_context_set_font_options ( p, fo );
+ // Setup dpi
+ if ( config.dpi > 0 ) {
+ PangoFontMap *font_map = pango_cairo_font_map_get_default ();
+ pango_cairo_font_map_set_resolution ( (PangoCairoFontMap *) font_map, (double) config.dpi );
+ }
+ // Setup font.
+ PangoFontDescription *pfd = pango_font_description_from_string ( config.menu_font );
+ pango_context_set_font_description ( p, pfd );
+ pango_font_description_free ( pfd );
+ // Tell textbox to use this context.
+ textbox_set_pango_context ( p );
+ // cleanup
+ g_object_unref ( p );
+ cairo_font_options_destroy ( fo );
+
+ // // make it an unmanaged window
+ if ( !normal_window_mode && !config.fullscreen ) {
+ window_set_atom_prop ( display, box, netatoms[_NET_WM_STATE], &netatoms[_NET_WM_STATE_ABOVE], 1 );
+ XSetWindowAttributes sattr = { .override_redirect = True };
+ XChangeWindowAttributes ( display, box, CWOverrideRedirect, &sattr );
+ }
+ else{
+ window_set_atom_prop ( display, box, netatoms[_NET_WM_WINDOW_TYPE], &netatoms[_NET_WM_WINDOW_TYPE_NORMAL], 1 );
+ }
+ if ( config.fullscreen ) {
+ Atom atoms[] = {
+ netatoms[_NET_WM_STATE_FULLSCREEN],
+ netatoms[_NET_WM_STATE_ABOVE]
+ };
+ window_set_atom_prop ( display, box, netatoms[_NET_WM_STATE], atoms, sizeof ( atoms ) / sizeof ( Atom ) );
+ }
+
+ xim = XOpenIM ( display, NULL, NULL, NULL );
+ xic = XCreateIC ( xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow,
+ box, XNFocusWindow, box, NULL );
+
+ // Set the WM_NAME
+ XStoreName ( display, box, "rofi" );
+
+ x11_set_window_opacity ( display, box, config.window_opacity );
+ return box;
+}
+/**
+ * Small wrapper function to create easy workers.
+ */
+void rofi_view_call_thread ( gpointer data, gpointer user_data )
+{
+ thread_state *t = (thread_state *) data;
+ t->callback ( t, user_data );
+}
+
+/**
+ * @param state Internal state of the menu.
+ *
+ * Calculate the number of rows, columns and elements to display based on the
+ * configuration and available data.
+ */
+static void menu_calculate_rows_columns ( RofiViewState *state )
+{
+ state->columns = config.menu_columns;
+ state->max_elements = MIN ( state->menu_lines * state->columns, state->num_lines );
+
+ // Calculate the number or rows. We do this by getting the num_lines rounded up to X columns
+ // (num elements is better name) then dividing by columns.
+ state->max_rows = MIN ( state->menu_lines, (unsigned int) ( ( state->num_lines + ( state->columns - state->num_lines %
+ state->columns ) %
+ state->columns ) / ( state->columns ) ) );
+ // Always have at least one row.
+ state->max_rows = MAX ( 1, state->max_rows );
+
+ if ( config.fixed_num_lines == TRUE ) {
+ state->max_elements = state->menu_lines * state->columns;
+ state->max_rows = state->menu_lines;
+ // If it would fit in one column, only use one column.
+ if ( state->num_lines < state->max_elements ) {
+ state->columns =
+ ( state->num_lines + ( state->max_rows - state->num_lines % state->max_rows ) % state->max_rows ) / state->max_rows;
+ state->max_elements = state->menu_lines * state->columns;
+ }
+ // Sanitize.
+ if ( state->columns == 0 ) {
+ state->columns = 1;
+ }
+ }
+}
+
+/**
+ * @param state Internal state of the menu.
+ * @param mon the dimensions of the monitor rofi is displayed on.
+ *
+ * Calculate the width of the window and the width of an element.
+ */
+static void menu_calculate_window_and_element_width ( RofiViewState *state, workarea *mon )
+{
+ if ( config.menu_width < 0 ) {
+ double fw = textbox_get_estimated_char_width ( );
+ state->w = -( fw * config.menu_width );
+ state->w += 2 * state->border + 4; // 4 = 2*SIDE_MARGIN
+ }
+ else{
+ // Calculate as float to stop silly, big rounding down errors.
+ state->w = config.menu_width < 101 ? ( mon->w / 100.0f ) * ( float ) config.menu_width : config.menu_width;
+ }
+
+ if ( state->columns > 0 ) {
+ state->element_width = state->w - ( 2 * ( state->border ) );
+ // Divide by the # columns
+ state->element_width = ( state->element_width - ( state->columns - 1 ) * config.line_margin ) / state->columns;
+ }
+}
+
+/**
+ * Nav helper functions, to avoid duplicate code.
+ */
+/**
+ * @param state The current RofiViewState
+ *
+ * Move the selection one page down.
+ * - No wrap around.
+ * - Clip at top/bottom
+ */
+inline static void menu_nav_page_next ( RofiViewState *state )
+{
+ // If no lines, do nothing.
+ if ( state->filtered_lines == 0 ) {
+ return;
+ }
+ state->selected += ( state->max_elements );
+ if ( state->selected >= state->filtered_lines ) {
+ state->selected = state->filtered_lines - 1;
+ }
+ state->update = TRUE;
+}
+/**
+ * @param state The current RofiViewState
+ *
+ * Move the selection one page up.
+ * - No wrap around.
+ * - Clip at top/bottom
+ */
+inline static void menu_nav_page_prev ( RofiViewState * state )
+{
+ if ( state->selected < state->max_elements ) {
+ state->selected = 0;
+ }
+ else{
+ state->selected -= ( state->max_elements );
+ }
+ state->update = TRUE;
+}
+/**
+ * @param state The current RofiViewState
+ *
+ * Move the selection one column to the right.
+ * - No wrap around.
+ * - Do not move to top row when at start.
+ */
+inline static void menu_nav_right ( RofiViewState *state )
+{
+ // If no lines, do nothing.
+ if ( state->filtered_lines == 0 ) {
+ return;
+ }
+ if ( ( state->selected + state->max_rows ) < state->filtered_lines ) {
+ state->selected += state->max_rows;
+ state->update = TRUE;
+ }
+ else if ( state->selected < ( state->filtered_lines - 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 = state->selected / state->max_rows;
+ // Check total number of columns.
+ int ncol = state->filtered_lines / state->max_rows;
+ // If there is an extra column, move.
+ if ( col != ncol ) {
+ state->selected = state->filtered_lines - 1;
+ state->update = TRUE;
+ }
+ }
+}
+/**
+ * @param state The current RofiViewState
+ *
+ * Move the selection one column to the left.
+ * - No wrap around.
+ */
+inline static void menu_nav_left ( RofiViewState *state )
+{
+ if ( state->selected >= state->max_rows ) {
+ state->selected -= state->max_rows;
+ state->update = TRUE;
+ }
+}
+/**
+ * @param state The current RofiViewState
+ *
+ * Move the selection one row up.
+ * - Wrap around.
+ */
+inline static void menu_nav_up ( RofiViewState *state )
+{
+ // Wrap around.
+ if ( state->selected == 0 ) {
+ state->selected = state->filtered_lines;
+ }
+
+ if ( state->selected > 0 ) {
+ state->selected--;
+ }
+ state->update = TRUE;
+}
+/**
+ * @param state The current RofiViewState
+ *
+ * Move the selection one row down.
+ * - Wrap around.
+ */
+inline static void menu_nav_down ( RofiViewState *state )
+{
+ // If no lines, do nothing.
+ if ( state->filtered_lines == 0 ) {
+ return;
+ }
+ state->selected = state->selected < state->filtered_lines - 1 ? MIN ( state->filtered_lines - 1, state->selected + 1 ) : 0;
+ state->update = TRUE;
+}
+/**
+ * @param state The current RofiViewState
+ *
+ * Move the selection to first row.
+ */
+inline static void menu_nav_first ( RofiViewState * state )
+{
+ state->selected = 0;
+ state->update = TRUE;
+}
+/**
+ * @param state The current RofiViewState
+ *
+ * Move the selection to last row.
+ */
+inline static void menu_nav_last ( RofiViewState * state )
+{
+ // If no lines, do nothing.
+ if ( state->filtered_lines == 0 ) {
+ return;
+ }
+ state->selected = state->filtered_lines - 1;
+ state->update = TRUE;
+}
+static void menu_draw ( RofiViewState *state, cairo_t *d )
+{
+ unsigned int i, offset = 0;
+ // selected row is always visible.
+ // If selected is visible do not scroll.
+ if ( ( ( state->selected - ( state->last_offset ) ) < ( state->max_elements ) ) && ( state->selected >= ( state->last_offset ) ) ) {
+ offset = state->last_offset;
+ }
+ else{
+ // Do paginating
+ int page = ( state->max_elements > 0 ) ? ( state->selected / state->max_elements ) : 0;
+ offset = page * state->max_elements;
+ state->last_offset = offset;
+ if ( page != state->cur_page ) {
+ state->cur_page = page;
+ state->rchanged = TRUE;
+ }
+ // Set the position
+ scrollbar_set_handle ( state->scrollbar, page * state->max_elements );
+ }
+ // Re calculate the boxes and sizes, see if we can move this in the menu_calc*rowscolumns
+ // Get number of remaining lines to display.
+ unsigned int a_lines = MIN ( ( state->filtered_lines - offset ), state->max_elements );
+
+ // Calculate number of columns
+ unsigned int columns = ( a_lines + ( state->max_rows - a_lines % state->max_rows ) % state->max_rows ) / state->max_rows;
+ columns = MIN ( columns, state->columns );
+
+ // Update the handle length.
+ scrollbar_set_handle_length ( state->scrollbar, columns * state->max_rows );
+ scrollbar_draw ( state->scrollbar, d );
+ // Element width.
+ unsigned int element_width = state->w - ( 2 * ( state->border ) );
+ if ( state->scrollbar != NULL ) {
+ element_width -= state->scrollbar->widget.w;
+ }
+ if ( columns > 0 ) {
+ element_width = ( element_width - ( columns - 1 ) * config.line_margin ) / columns;
+ }
+
+ int element_height = state->line_height * config.element_height;
+ int y_offset = state->top_offset;
+ int x_offset = state->border;
+ // Calculate number of visible rows.
+ unsigned int max_elements = MIN ( a_lines, state->max_rows * columns );
+
+ if ( state->rchanged ) {
+ // Move, resize visible boxes and show them.
+ for ( i = 0; i < max_elements; i++ ) {
+ unsigned int ex = ( ( i ) / state->max_rows ) * ( element_width + config.line_margin );
+ unsigned int ey = ( ( i ) % state->max_rows ) * ( element_height + config.line_margin );
+ // Move it around.
+ textbox_moveresize ( state->boxes[i], ex + x_offset, ey + y_offset, element_width, element_height );
+ {
+ TextBoxFontType type = ( ( ( i % state->max_rows ) & 1 ) == 0 ) ? NORMAL : ALT;
+ int fstate = 0;
+ char *text = mode_get_display_value ( state->sw, state->line_map[i + offset], &fstate, TRUE );
+ TextBoxFontType tbft = fstate | ( ( i + offset ) == state->selected ? HIGHLIGHT : type );
+ textbox_font ( state->boxes[i], tbft );
+ textbox_text ( state->boxes[i], text );
+ g_free ( text );
+ }
+ textbox_draw ( state->boxes[i], d );
+ }
+ state->rchanged = FALSE;
+ }
+ else{
+ // Only do basic redrawing + highlight of row.
+ for ( i = 0; i < max_elements; i++ ) {
+ TextBoxFontType type = ( ( ( i % state->max_rows ) & 1 ) == 0 ) ? NORMAL : ALT;
+ int fstate = 0;
+ mode_get_display_value ( state->sw, state->line_map[i + offset], &fstate, FALSE );
+ TextBoxFontType tbft = fstate | ( ( i + offset ) == state->selected ? HIGHLIGHT : type );
+ textbox_font ( state->boxes[i], tbft );
+ textbox_draw ( state->boxes[i], d );
+ }
+ }
+}
+
+void menu_update ( RofiViewState *state )
+{
+ TICK ();
+ cairo_surface_t * surf = cairo_image_surface_create ( get_format (), state->w, state->h );
+ cairo_t *d = cairo_create ( surf );
+ cairo_set_operator ( d, CAIRO_OPERATOR_SOURCE );
+ if ( config.fake_transparency ) {
+ if ( fake_bg != NULL ) {
+ cairo_set_source_surface ( d, fake_bg,
+ -(double) ( state->x - state->mon.x ),
+ -(double) ( state->y - state->mon.y ) );
+ cairo_paint ( d );
+ cairo_set_operator ( d, CAIRO_OPERATOR_OVER );
+ color_background ( display, d );
+ cairo_paint ( d );
+ }
+ }
+ else {
+ // Paint the background.
+ color_background ( display, d );
+ cairo_paint ( d );
+ }
+ TICK_N ( "Background" );
+ color_border ( display, d );
+
+ if ( config.menu_bw > 0 ) {
+ cairo_save ( d );
+ cairo_set_line_width ( d, config.menu_bw );
+ cairo_rectangle ( d,
+ config.menu_bw / 2.0,
+ config.menu_bw / 2.0,
+ state->w - config.menu_bw,
+ state->h - config.menu_bw );
+ cairo_stroke ( d );
+ cairo_restore ( d );
+ }
+
+ // Always paint as overlay over the background.
+ cairo_set_operator ( d, CAIRO_OPERATOR_OVER );
+ if ( state->max_elements > 0 ) {
+ menu_draw ( state, d );
+ }
+ if ( state->prompt_tb ) {
+ textbox_draw ( state->prompt_tb, d );
+ }
+ if ( state->text ) {
+ textbox_draw ( state->text, d );
+ }
+ if ( state->case_indicator ) {
+ textbox_draw ( state->case_indicator, d );
+ }
+ if ( state->message_tb ) {
+ textbox_draw ( state->message_tb, d );
+ }
+ color_separator ( display, d );
+
+ if ( strcmp ( config.separator_style, "none" ) ) {
+ if ( strcmp ( config.separator_style, "dash" ) == 0 ) {
+ const double dashes[1] = { 4 };
+ cairo_set_dash ( d, dashes, 1, 0.0 );
+ }
+ cairo_move_to ( d, state->border, state->line_height + ( state->border ) * 1 + config.line_margin + 1 );
+ cairo_line_to ( d, state->w - state->border, state->line_height + ( state->border ) * 1 + config.line_margin + 1 );
+ cairo_stroke ( d );
+ if ( state->message_tb ) {
+ cairo_move_to ( d, state->border, state->top_offset - ( config.line_margin ) - 1 );
+ cairo_line_to ( d, state->w - state->border, state->top_offset - ( config.line_margin ) - 1 );
+ cairo_stroke ( d );
+ }
+
+ if ( config.sidebar_mode == TRUE ) {
+ cairo_move_to ( d, state->border, state->h - state->line_height - ( state->border ) * 1 - 1 - config.line_margin );
+ cairo_line_to ( d, state->w - state->border, state->h - state->line_height - ( state->border ) * 1 - 1 - config.line_margin );
+ cairo_stroke ( d );
+ }
+ }
+ if ( config.sidebar_mode == TRUE ) {
+ for ( unsigned int j = 0; j < num_modi; j++ ) {
+ if ( state->modi[j] != NULL ) {
+ textbox_draw ( state->modi[j], d );
+ }
+ }
+ }
+ state->update = FALSE;
+
+ // Draw to actual window.
+ cairo_set_source_surface ( draw, surf, 0, 0 );
+ cairo_paint ( draw );
+ // Cleanup
+ cairo_destroy ( d );
+ cairo_surface_destroy ( surf );
+
+ // Flush the surface.
+ cairo_surface_flush ( surface );
+ TICK ();
+}
+
+/**
+ * @param state Internal state of the menu.
+ * @param xse X selection event.
+ *
+ * Handle paste event.
+ */
+static void menu_paste ( RofiViewState *state, XSelectionEvent *xse )
+{
+ if ( xse->property == netatoms[UTF8_STRING] ) {
+ gchar *text = window_get_text_prop ( display, main_window, netatoms[UTF8_STRING] );
+ if ( text != NULL && text[0] != '\0' ) {
+ unsigned int dl = strlen ( text );
+ // Strip new line
+ while ( dl > 0 && text[dl] == '\n' ) {
+ text[dl] = '\0';
+ dl--;
+ }
+ // Insert string move cursor.
+ textbox_insert ( state->text, state->text->cursor, text, dl );
+ textbox_cursor ( state->text, state->text->cursor + dl );
+ // Force a redraw and refiltering of the text.
+ state->update = TRUE;
+ state->refilter = TRUE;
+ }
+ g_free ( text );
+ }
+}
+
+static void menu_resize ( RofiViewState *state )
+{
+ unsigned int sbw = config.line_margin + 8;
+ widget_move ( WIDGET ( state->scrollbar ), state->w - state->border - sbw, state->top_offset );
+ if ( config.sidebar_mode == TRUE ) {
+ int width = ( state->w - ( 2 * ( state->border ) + ( num_modi - 1 ) * config.line_margin ) ) / num_modi;
+ for ( unsigned int j = 0; j < num_modi; j++ ) {
+ textbox_moveresize ( state->modi[j],
+ state->border + j * ( width + config.line_margin ), state->h - state->line_height - state->border,
+ width, state->line_height );
+ textbox_draw ( state->modi[j], draw );
+ }
+ }
+ int entrybox_width = state->w - ( 2 * ( state->border ) ) - textbox_get_width ( state->prompt_tb )
+ - textbox_get_width ( state->case_indicator );
+ textbox_moveresize ( state->text, state->text->widget.x, state->text->widget.y, entrybox_width, state->line_height );
+ widget_move ( WIDGET ( state->case_indicator ), state->w - state->border - textbox_get_width ( state->case_indicator ), state->border );
+ /**
+ * Resize in Height
+ */
+ {
+ unsigned int last_length = state->max_elements;
+ int element_height = state->line_height * config.element_height + config.line_margin;
+ // Calculated new number of boxes.
+ int h = ( state->h - state->top_offset - config.padding );
+ if ( config.sidebar_mode == TRUE ) {
+ h -= state->line_height + config.line_margin;
+ }
+ if ( h < 0 ) {
+ fprintf ( stderr, "Current padding %u (on each side) does not fit within visible window %u.\n", config.padding, state->h );
+ h = ( state->h - state->top_offset - state->h / 3 );
+ if ( config.sidebar_mode == TRUE ) {
+ h -= state->line_height + config.line_margin;
+ }
+ }
+ state->max_rows = MAX ( 1, ( h / element_height ) );
+ state->max_elements = state->max_rows * config.menu_columns;
+ // Free boxes no longer needed.
+ for ( unsigned int i = state->max_elements; i < last_length; i++ ) {
+ textbox_free ( state->boxes[i] );
+ }
+ // resize array.
+ state->boxes = g_realloc ( state->boxes, state->max_elements * sizeof ( textbox* ) );
+
+ int y_offset = state->top_offset;
+ int x_offset = state->border;
+ int rstate = 0;
+ if ( config.markup_rows ) {
+ rstate = TB_MARKUP;
+ }
+ // Add newly added boxes.
+ for ( unsigned int i = last_length; i < state->max_elements; i++ ) {
+ state->boxes[i] = textbox_create ( rstate, x_offset, y_offset,
+ state->element_width, element_height, NORMAL, "" );
+ }
+ scrollbar_resize ( state->scrollbar, -1, ( state->max_rows ) * ( element_height ) - config.line_margin );
+ }
+
+ state->rchanged = TRUE;
+ state->update = TRUE;
+}
+
+/**
+ * @param state Internal state of the menu.
+ * @param key the Key being pressed.
+ * @param modstate the modifier state.
+ *
+ * Keyboard navigation through the elements.
+ */
+static int menu_keyboard_navigation ( RofiViewState *state, KeySym key, unsigned int modstate )
+{
+ // pressing one of the global key bindings closes the switcher. This allows fast closing of the
+ // menu if an item is not selected
+ if ( locate_switcher ( key, modstate ) != -1 || abe_test_action ( CANCEL, modstate, key ) ) {
+ state->retv = MENU_CANCEL;
+ state->quit = TRUE;
+ return 1;
+ }
+ // Up, Ctrl-p or Shift-Tab
+ else if ( abe_test_action ( ROW_UP, modstate, key ) ) {
+ menu_nav_up ( state );
+ return 1;
+ }
+ else if ( abe_test_action ( ROW_TAB, modstate, key ) ) {
+ if ( state->filtered_lines == 1 ) {
+ state->retv = MENU_OK;
+ ( state->selected_line ) = state->line_map[state->selected];
+ state->quit = 1;
+ return 1;
+ }
+
+ // Double tab!
+ if ( state->filtered_lines == 0 && key == state->prev_key ) {
+ state->retv = MENU_NEXT;
+ ( state->selected_line ) = 0;
+ state->quit = TRUE;
+ }
+ else{
+ menu_nav_down ( state );
+ }
+ return 1;
+ }
+ // Down, Ctrl-n
+ else if ( abe_test_action ( ROW_DOWN, modstate, key ) ) {
+ menu_nav_down ( state );
+ return 1;
+ }
+ else if ( abe_test_action ( ROW_LEFT, modstate, key ) ) {
+ menu_nav_left ( state );
+ return 1;
+ }
+ else if ( abe_test_action ( ROW_RIGHT, modstate, key ) ) {
+ menu_nav_right ( state );
+ return 1;
+ }
+ else if ( abe_test_action ( PAGE_PREV, modstate, key ) ) {
+ menu_nav_page_prev ( state );
+ return 1;
+ }
+ else if ( abe_test_action ( PAGE_NEXT, modstate, key ) ) {
+ menu_nav_page_next ( state );
+ return 1;
+ }
+ else if ( abe_test_action ( ROW_FIRST, modstate, key ) ) {
+ menu_nav_first ( state );
+ return 1;
+ }
+ else if ( abe_test_action ( ROW_LAST, modstate, key ) ) {
+ menu_nav_last ( state );
+ return 1;
+ }
+ else if ( abe_test_action ( ROW_SELECT, modstate, key ) ) {
+ // If a valid item is selected, return that..
+ if ( state->selected < state->filtered_lines ) {
+ char *str = mode_get_completion ( state->sw, state->line_map[state->selected] );
+ textbox_text ( state->text, str );
+ g_free ( str );
+ textbox_cursor_end ( state->text );
+ state->update = TRUE;
+ state->refilter = TRUE;
+ }
+ return 1;
+ }
+ state->prev_key = key;
+ return 0;
+}
+
+static void menu_mouse_navigation ( RofiViewState *state, XButtonEvent *xbe )
+{
+ // Scroll event
+ if ( xbe->button > 3 ) {
+ if ( xbe->button == 4 ) {
+ menu_nav_up ( state );
+ }
+ else if ( xbe->button == 5 ) {
+ menu_nav_down ( state );
+ }
+ else if ( xbe->button == 6 ) {
+ menu_nav_left ( state );
+ }
+ else if ( xbe->button == 7 ) {
+ menu_nav_right ( state );
+ }
+ return;
+ }
+ else {
+ if ( state->scrollbar && widget_intersect ( &( state->scrollbar->widget ), xbe->x, xbe->y ) ) {
+ state->selected = scrollbar_clicked ( state->scrollbar, xbe->y );
+ state->update = TRUE;
+ return;
+ }
+ for ( unsigned int i = 0; config.sidebar_mode == TRUE && i < num_modi; i++ ) {
+ if ( widget_intersect ( &( state->modi[i]->widget ), xbe->x, xbe->y ) ) {
+ ( state->selected_line ) = 0;
+ state->retv = MENU_QUICK_SWITCH | ( i & MENU_LOWER_MASK );
+ state->quit = TRUE;
+ state->skip_absorb = TRUE;
+ return;
+ }
+ }
+ for ( unsigned int i = 0; i < state->max_elements; i++ ) {
+ if ( widget_intersect ( &( state->boxes[i]->widget ), xbe->x, xbe->y ) ) {
+ // Only allow items that are visible to be selected.
+ if ( ( state->last_offset + i ) >= state->filtered_lines ) {
+ break;
+ }
+ //
+ state->selected = state->last_offset + i;
+ state->update = TRUE;
+ if ( ( xbe->time - state->last_button_press ) < 200 ) {
+ state->retv = MENU_OK;
+ ( state->selected_line ) = state->line_map[state->selected];
+ // Quit
+ state->quit = TRUE;
+ state->skip_absorb = TRUE;
+ }
+ state->last_button_press = xbe->time;
+ break;
+ }
+ }
+ }
+}
+static void menu_refilter ( RofiViewState *state )
+{
+ TICK_N ( "Filter start" );
+ if ( strlen ( state->text->text ) > 0 ) {
+ unsigned int j = 0;
+ char **tokens = tokenize ( state->text->text, config.case_sensitive );
+ /**
+ * On long lists it can be beneficial to parallelize.
+ * If number of threads is 1, no thread is spawn.
+ * If number of threads > 1 and there are enough (> 1000) items, spawn jobs for the thread pool.
+ * For large lists with 8 threads I see a factor three speedup of the whole function.
+ */
+ unsigned int nt = MAX ( 1, state->num_lines / 500 );
+ thread_state states[nt];
+ GCond cond;
+ GMutex mutex;
+ g_mutex_init ( &mutex );
+ g_cond_init ( &cond );
+ unsigned int count = nt;
+ unsigned int steps = ( state->num_lines + nt ) / nt;
+ for ( unsigned int i = 0; i < nt; i++ ) {
+ states[i].state = state;
+ states[i].tokens = tokens;
+ states[i].start = i * steps;
+ states[i].stop = MIN ( state->num_lines, ( i + 1 ) * steps );
+ states[i].count = 0;
+ states[i].cond = &cond;
+ states[i].mutex = &mutex;
+ states[i].acount = &count;
+ states[i].callback = filter_elements;
+ if ( i > 0 ) {
+ g_thread_pool_push ( tpool, &states[i], NULL );
+ }
+ }
+ // Run one in this thread.
+ filter_elements ( &states[0], NULL );
+ // No need to do this with only one thread.
+