diff --git a/Makefile.am b/Makefile.am index a5446d97..42b5f594 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ rofi_SOURCES=\ source/rofi.c\ source/i3-support.c\ source/textbox.c\ + source/scrollbar.c\ source/xrmoptions.c\ source/history.c\ config/config.c\ @@ -47,6 +48,7 @@ rofi_SOURCES=\ include/xrmoptions.h\ include/history.h\ include/textbox.h\ + include/scrollbar.h\ include/helper.h\ include/keyb.h\ include/x11-helper.h\ @@ -154,13 +156,21 @@ test: ${bin_PROGRAMS} .PHONY: test-x test-x: ${bin_PROGRAMS} + echo "Test 1" $(top_srcdir)/test/run_test.sh 123 $(top_builddir)/textbox_test $(top_builddir) $(top_srcdir)/doc/example.xresources + echo "Test 2" $(top_srcdir)/test/run_test.sh 200 $(top_srcdir)/test/run_errormsg_test.sh $(top_builddir) $(top_srcdir)/doc/example.xresources + echo "Test 3" $(top_srcdir)/test/run_test.sh 201 $(top_srcdir)/test/run_switchdialog_test.sh $(top_builddir) $(top_srcdir)/doc/example.xresources + echo "Test 4" $(top_srcdir)/test/run_test.sh 202 $(top_srcdir)/test/run_dmenu_test.sh $(top_builddir) $(top_srcdir)/doc/example.xresources + echo "Test 5" $(top_srcdir)/test/run_test.sh 203 $(top_srcdir)/test/run_dmenu_custom_test.sh $(top_builddir) $(top_srcdir)/doc/example.xresources + echo "Test 6" $(top_srcdir)/test/run_test.sh 204 $(top_srcdir)/test/run_run_test.sh $(top_builddir) $(top_srcdir)/doc/example.xresources + echo "Test 7" $(top_srcdir)/test/run_test.sh 205 $(top_srcdir)/test/run_script_test.sh $(top_builddir) $(top_srcdir)/doc/example.xresources + echo "End tests" .PHONY: indent diff --git a/include/scrollbar.h b/include/scrollbar.h new file mode 100644 index 00000000..df6992a5 --- /dev/null +++ b/include/scrollbar.h @@ -0,0 +1,93 @@ +#ifndef ROFI_SCROLLBAR_H +#define ROFI_SCROLLBAR_H + +/** + * Internal structure for the scrollbar. + */ +typedef struct _scrollbar +{ + Window window, parent; + short x, y, w, h; + GC gc; + unsigned int length; + unsigned int pos; + unsigned int pos_length; +} scrollbar; + +/** + * @param parent the parent window + * @param vinfo The XVisualInfo to use when creating new window + * @param map The colormap to use for new window + * @param x The x coordinate (relative to parent) to position the new scrollbar + * @param y The y coordinate (relative to parent) to position the new scrollbar + * @param w The width of the scrollbar + * @param h The height of the scrollbar + * + * Create a new scrollbar + * + * @returns the scrollbar object. + */ +scrollbar *scrollbar_create ( Window parent, XVisualInfo *vinfo, Colormap map, + short x, short y, short w, short h ); + +/** + * @param sb scrollbar object + * + * Hide (unmap) the scrollbar. + */ +void scrollbar_hide ( scrollbar *sb ); + +/** + * @param sb scrollbar object + * + * Show (map) the scrollbar. + */ +void scrollbar_show ( scrollbar *sb ); + +/** + * @param sb scrollbar object + * + * Free the resources used by the scrollbar. + */ +void scrollbar_free ( scrollbar *sb ); + +/** + * @param sb scrollbar object + * @param pos_length new length + * + * set the length of the handle relative to the max value of bar. + */ +void scrollbar_set_handle_length ( scrollbar *sb, unsigned int pos_length ); + +/** + * @param sb scrollbar object + * @param pos new position + * + * set the position of the handle relative to the set max value of bar. + */ +void scrollbar_set_handle ( scrollbar *sb, unsigned int pos ); + +/** + * @param sb scrollbar object + * @param max the new max + * + * set the max value of the bar. + */ +void scrollbar_set_max_value ( scrollbar *sb, unsigned int max ); + +/** + * @param sb scrollbar object + * + * Draw the scrollbar, used after expose event or update + */ +void scrollbar_draw ( scrollbar *sb ); + +/** + * @param sb scrollbar object + * @param y clicked position + * + * Calculate the position of the click relative to the max value of bar + */ +unsigned int scrollbar_clicked ( scrollbar *sb, int y ); + +#endif // ROFI_SCROLLBAR_H diff --git a/include/textbox.h b/include/textbox.h index 4293ddee..9c9827e1 100644 --- a/include/textbox.h +++ b/include/textbox.h @@ -1,6 +1,5 @@ #ifndef ROFI_TEXTBOX_H #define ROFI_TEXTBOX_H -#include #include #include @@ -25,13 +24,13 @@ typedef struct typedef enum { - TB_AUTOHEIGHT = 1 << 0, - TB_AUTOWIDTH = 1 << 1, - TB_LEFT = 1 << 16, - TB_RIGHT = 1 << 17, - TB_CENTER = 1 << 18, - TB_EDITABLE = 1 << 19, - TB_MARKUP = 1 << 20, + TB_AUTOHEIGHT = 1 << 0, + TB_AUTOWIDTH = 1 << 1, + TB_LEFT = 1 << 16, + TB_RIGHT = 1 << 17, + TB_CENTER = 1 << 18, + TB_EDITABLE = 1 << 19, + TB_MARKUP = 1 << 20, } TextboxFlags; diff --git a/source/rofi.c b/source/rofi.c index 77f2bb61..edd1aace 100644 --- a/source/rofi.c +++ b/source/rofi.c @@ -47,6 +47,7 @@ #include "rofi.h" #include "helper.h" #include "textbox.h" +#include "scrollbar.h" #include "x11-helper.h" #include "xrmoptions.h" #include "dialogs/dialogs.h" @@ -128,45 +129,6 @@ static inline MainLoopEvent wait_for_xevent_or_timeout ( Display *display, int x return ML_XEVENT; } -static void menu_hide_arrow_text ( int filtered_lines, int selected, int max_elements, - textbox *arrowbox_top, textbox *arrowbox_bottom ) -{ - if ( arrowbox_top == NULL || arrowbox_bottom == NULL ) { - return; - } - int page = ( filtered_lines > 0 ) ? selected / max_elements : 0; - int npages = ( filtered_lines > 0 ) ? ( ( filtered_lines + max_elements - 1 ) / max_elements ) : 1; - if ( !( page != 0 && npages > 1 ) ) { - textbox_hide ( arrowbox_top ); - } - if ( !( ( npages - 1 ) != page && npages > 1 ) ) { - textbox_hide ( arrowbox_bottom ); - } -} - -static void menu_set_arrow_text ( int filtered_lines, int selected, int max_elements, - textbox *arrowbox_top, textbox *arrowbox_bottom ) -{ - if ( arrowbox_top == NULL || arrowbox_bottom == NULL ) { - return; - } - if ( filtered_lines == 0 || max_elements == 0 ) { - return; - } - int page = ( filtered_lines > 0 ) ? selected / max_elements : 0; - int npages = ( filtered_lines > 0 ) ? ( ( filtered_lines + max_elements - 1 ) / max_elements ) : 1; - int entry = selected % max_elements; - if ( page != 0 && npages > 1 ) { - textbox_show ( arrowbox_top ); - textbox_font ( arrowbox_top, ( entry != 0 ) ? NORMAL : HIGHLIGHT ); - textbox_draw ( arrowbox_top ); - } - if ( ( npages - 1 ) != page && npages > 1 ) { - textbox_show ( arrowbox_bottom ); - textbox_font ( arrowbox_bottom, ( entry != ( max_elements - 1 ) ) ? NORMAL : HIGHLIGHT ); - textbox_draw ( arrowbox_bottom ); - } -} /** * Levenshtein Sorting. @@ -287,9 +249,8 @@ typedef struct MenuState textbox *prompt_tb; textbox *message_tb; textbox *case_indicator; - textbox *arrowbox_top; - textbox *arrowbox_bottom; textbox **boxes; + scrollbar *scrollbar; int *distance; int *line_map; @@ -325,8 +286,7 @@ static void menu_free_state ( MenuState *state ) textbox_free ( state->text ); textbox_free ( state->prompt_tb ); textbox_free ( state->case_indicator ); - textbox_free ( state->arrowbox_bottom ); - textbox_free ( state->arrowbox_top ); + scrollbar_free ( state->scrollbar ); for ( unsigned int i = 0; i < state->max_elements; i++ ) { textbox_free ( state->boxes[i] ); @@ -698,25 +658,12 @@ static void menu_mouse_navigation ( MenuState *state, XButtonEvent *xbe ) } return; } - if ( xbe->window == state->arrowbox_top->window ) { - // Page up. - if ( state->selected < state->max_rows ) { - state->selected = 0; - } - else{ - state->selected -= state->max_elements; - } - state->update = TRUE; - } - else if ( xbe->window == state->arrowbox_bottom->window ) { - // Page down. - state->selected += state->max_elements; - if ( state->selected >= state->filtered_lines ) { - state->selected = state->filtered_lines - 1; - } - state->update = TRUE; - } else { + if ( state->scrollbar && state->scrollbar->window == xbe->window ) { + state->selected = scrollbar_clicked ( state->scrollbar, xbe->y ); + state->update = TRUE; + return; + } for ( unsigned int i = 0; config.sidebar_mode == TRUE && i < num_switchers; i++ ) { if ( switchers[i]->tb->window == ( xbe->window ) ) { *( state->selected_line ) = 0; @@ -791,6 +738,7 @@ static void menu_refilter ( MenuState *state, char **lines, menu_match_cb mmc, v state->quit = TRUE; } + scrollbar_set_max_value ( state->scrollbar, state->filtered_lines ); state->refilter = FALSE; state->rchanged = TRUE; } @@ -815,8 +763,9 @@ static void menu_draw ( MenuState *state ) 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 ); @@ -826,6 +775,9 @@ static void menu_draw ( MenuState *state ) 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 ); // Element width. unsigned int element_width = state->w - ( 2 * ( config.padding ) ); if ( columns > 0 ) { @@ -879,9 +831,6 @@ static void menu_draw ( MenuState *state ) static void menu_update ( MenuState *state ) { - menu_hide_arrow_text ( state->filtered_lines, state->selected, - state->max_elements, state->arrowbox_top, - state->arrowbox_bottom ); textbox_draw ( state->case_indicator ); textbox_draw ( state->prompt_tb ); textbox_draw ( state->text ); @@ -889,9 +838,6 @@ static void menu_update ( MenuState *state ) textbox_draw ( state->message_tb ); } menu_draw ( state ); - menu_set_arrow_text ( state->filtered_lines, state->selected, - state->max_elements, state->arrowbox_top, - state->arrowbox_bottom ); // Why do we need the special -1? XDrawLine ( display, main_window, gc, 0, state->line_height + ( config.padding ) * 1 + config.line_margin + 1, @@ -1076,22 +1022,11 @@ MenuReturn menu ( char **lines, unsigned int num_lines, char **input, char *prom x_offset, y_offset, state.element_width, element_height, NORMAL, "" ); } - // Arrows - state.arrowbox_top = textbox_create ( main_window, &vinfo, map, TB_AUTOWIDTH, - ( config.padding ), ( config.padding ), - 0, element_height, NORMAL, - "↑" ); - state.arrowbox_bottom = textbox_create ( main_window, &vinfo, map, TB_AUTOWIDTH, - ( config.padding ), ( config.padding ), - 0, element_height, NORMAL, - "↓" ); - textbox_move ( state.arrowbox_top, - state.w - config.padding - state.arrowbox_top->w, - state.top_offset ); - textbox_move ( state.arrowbox_bottom, - state.w - config.padding - state.arrowbox_bottom->w, - state.top_offset + ( state.max_rows - 1 ) * ( element_height + config.line_margin ) ); + state.scrollbar = scrollbar_create ( main_window, &vinfo, map, state.w - config.padding-config.line_margin- 8, state.top_offset, + config.line_margin+8, ( state.max_rows - 1 ) * ( element_height + config.line_margin ) + element_height ); + + scrollbar_set_max_value ( state.scrollbar, state.num_lines ); // filtered list state.line_map = g_malloc0_n ( state.num_lines, sizeof ( int ) ); if ( config.levenshtein_sort ) { @@ -1127,6 +1062,7 @@ MenuReturn menu ( char **lines, unsigned int num_lines, char **input, char *prom } } + scrollbar_show ( state.scrollbar ); // Display it. XMoveResizeWindow ( display, main_window, state.x, state.y, state.w, state.h ); XMapRaised ( display, main_window ); diff --git a/source/scrollbar.c b/source/scrollbar.c new file mode 100644 index 00000000..762ffed9 --- /dev/null +++ b/source/scrollbar.c @@ -0,0 +1,127 @@ +/** + * MIT/X11 License + * Modified (c) 2015 Qball Cow + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "scrollbar.h" +#include "rofi.h" +#include "x11-helper.h" + +extern Display *display; + +scrollbar *scrollbar_create ( Window parent, XVisualInfo *vinfo, Colormap map, + short x, short y, short w, short h ) +{ + scrollbar *sb = g_malloc0 ( sizeof ( scrollbar ) ); + + sb->parent = parent; + sb->x = x; + sb->y = y; + sb->w = MAX ( 1, w ); + sb->h = MAX ( 1, h ); + + sb->length = 10; + sb->pos = 0; + sb->pos_length = 4; + + XSetWindowAttributes attr; + attr.colormap = map; + attr.border_pixel = color_border ( display ); + attr.background_pixel = color_background ( display ); + sb->window = XCreateWindow ( display, sb->parent, sb->x, sb->y, sb->w, sb->h, 0, vinfo->depth, + InputOutput, vinfo->visual, CWColormap | CWBorderPixel | CWBackPixel, &attr ); + + XSelectInput ( display, sb->window, ExposureMask | ButtonPressMask ); + sb->gc = XCreateGC ( display, sb->window, 0, 0 ); + XSetForeground ( display, sb->gc, color_separator ( display ) ); + //XSetFillStyle ( display, sb->gc, FillSolid); + + // Create GC. + return sb; +} + +void scrollbar_show ( scrollbar *sb ) +{ + XMapWindow ( display, sb->window ); +} +void scrollbar_hide ( scrollbar *sb ) +{ + XUnmapWindow ( display, sb->window ); +} + +void scrollbar_free ( scrollbar *sb ) +{ + XFreeGC ( display, sb->gc ); + XDestroyWindow ( display, sb->window ); + g_free ( sb ); +} + +void scrollbar_set_max_value ( scrollbar *sb, unsigned int max ) +{ + sb->length = MAX ( 1, max ); +} + +void scrollbar_set_handle ( scrollbar *sb, unsigned int pos ) +{ + sb->pos = MIN ( sb->length, MAX ( 0, pos ) ); +} + +void scrollbar_set_handle_length ( scrollbar *sb, unsigned int pos_length ) +{ + sb->pos_length = MIN ( sb->length, MAX ( 1, pos_length ) ); +} + +void scrollbar_draw ( scrollbar *sb ) +{ + // Calculate position and size. + const short bh = sb->h - 0; + float sec = ( ( bh ) / (float) sb->length ); + short height = sb->pos_length * sec; + short y = sb->pos * sec; + // Set max pos. + y = MIN ( y, bh - 2 ); + // Never go out of bar. + height = MAX ( 2, height ); + // Cap length; + height = MIN ( bh - y + 1, ( height ) ); + // Redraw base window + XClearWindow ( display, sb->window ); + // Paint the handle. + XFillRectangle ( display, sb->window, sb->gc, config.line_margin, y, sb->w-config.line_margin, height ); +} + +unsigned int scrollbar_clicked ( scrollbar *sb, int y ) +{ + const short bh = sb->h - 2; + float sec = ( ( bh ) / (float) sb->length ); + unsigned int sel = y / sec; + return sel; +}