diff --git a/include/data.h b/include/data.h index 0f0a243d..3f35c595 100644 --- a/include/data.h +++ b/include/data.h @@ -436,8 +436,8 @@ struct Client { }; /** - * A container is either in default or stacking mode. It sits inside each cell - * of the table. + * A container is either in default, stacking or tabbed mode. There is one for + * each cell of the table. * */ struct Container { @@ -466,6 +466,15 @@ struct Container { /* Ensure MODE_DEFAULT maps to 0 because we use calloc for * initialization later */ enum { MODE_DEFAULT = 0, MODE_STACK, MODE_TABBED } mode; + + /* When in stacking, one can either have unlimited windows inside the + * container or set a limit for the rows or columns the stack window + * should display to use the screen more efficiently. */ + enum { STACK_LIMIT_NONE = 0, STACK_LIMIT_COLS, STACK_LIMIT_ROWS } stack_limit; + + /* The number of columns or rows to limit to, see stack_limit */ + int stack_limit_value; + CIRCLEQ_HEAD(client_head, Client) clients; }; diff --git a/src/click.c b/src/click.c index ac3f9440..4b27d1be 100644 --- a/src/click.c +++ b/src/click.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,19 @@ #include "floating.h" #include "resize.h" +static struct Stack_Window *get_stack_window(xcb_window_t window_id) { + struct Stack_Window *current; + + SLIST_FOREACH(current, &stack_wins, stack_windows) { + if (current->window != window_id) + continue; + + return current; + } + + return NULL; +} + /* * Checks if the button press was on a stack window, handles focus setting and returns true * if so, or false otherwise. @@ -43,45 +57,60 @@ */ static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event_t *event) { struct Stack_Window *stack_win; - SLIST_FOREACH(stack_win, &stack_wins, stack_windows) { - if (stack_win->window != event->event) - continue; - /* A stack window was clicked, we check if it was button4 or button5 - which are scroll up / scroll down. */ - if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) { - direction_t direction = (event->detail == XCB_BUTTON_INDEX_4 ? D_UP : D_DOWN); - focus_window_in_container(conn, CUR_CELL, direction); - return true; - } - - /* It was no scrolling, so we calculate the destination client by - dividing the Y position of the event through the height of a window - decoration and then set the focus to this client. */ - i3Font *font = load_font(conn, config.font); - int decoration_height = (font->height + 2 + 2); - int destination = (event->event_y / decoration_height), - c = 0, - num_clients = 0; - Client *client; - - CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients) - num_clients++; - - if (stack_win->container->mode == MODE_TABBED) - destination = (event->event_x / (stack_win->container->width / num_clients)); - - LOG("Click on stack_win for client %d\n", destination); - CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients) - if (c++ == destination) { - set_focus(conn, client, true); - return true; - } + /* If we find a corresponding stack window, we can handle the event */ + if ((stack_win = get_stack_window(event->event)) == NULL) + return false; + /* A stack window was clicked, we check if it was button4 or button5 + which are scroll up / scroll down. */ + if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) { + direction_t direction = (event->detail == XCB_BUTTON_INDEX_4 ? D_UP : D_DOWN); + focus_window_in_container(conn, CUR_CELL, direction); return true; } - return false; + /* It was no scrolling, so we calculate the destination client by + dividing the Y position of the event through the height of a window + decoration and then set the focus to this client. */ + i3Font *font = load_font(conn, config.font); + int decoration_height = (font->height + 2 + 2); + int destination = (event->event_y / decoration_height), + c = 0, + num_clients = 0; + Client *client; + Container *container = stack_win->container; + + CIRCLEQ_FOREACH(client, &(container->clients), clients) + num_clients++; + + if (container->mode == MODE_TABBED) + destination = (event->event_x / (container->width / num_clients)); + else if (container->mode == MODE_STACK && + container->stack_limit != STACK_LIMIT_NONE) { + if (container->stack_limit == STACK_LIMIT_COLS) { + int wrap = ceil((float)num_clients / container->stack_limit_value); + int clicked_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value)); + int clicked_row = (event->event_y / decoration_height); + LOG("clicked on column %d, row %d\n", clicked_column, clicked_row); + destination = (wrap * clicked_column) + clicked_row; + } else { + int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value)); + int clicked_column = (event->event_x / width); + int clicked_row = (event->event_y / decoration_height); + LOG("clicked on column %d, row %d\n", clicked_column, clicked_row); + destination = (container->stack_limit_value * clicked_column) + clicked_row; + } + } + + LOG("Click on stack_win for client %d\n", destination); + CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients) + if (c++ == destination) { + set_focus(conn, client, true); + return true; + } + + return true; } /* diff --git a/src/commands.c b/src/commands.c index 5ca92b31..191eb5bc 100644 --- a/src/commands.c +++ b/src/commands.c @@ -865,6 +865,30 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + if (STARTS_WITH(command, "stack-limit ")) { + if (last_focused == NULL || client_is_floating(last_focused)) { + LOG("No container focused\n"); + return; + } + const char *rest = command + strlen("stack-limit "); + if (strncmp(rest, "rows ", strlen("rows ")) == 0) { + last_focused->container->stack_limit = STACK_LIMIT_ROWS; + rest += strlen("rows "); + } else if (strncmp(rest, "cols ", strlen("cols ")) == 0) { + last_focused->container->stack_limit = STACK_LIMIT_COLS; + rest += strlen("cols "); + } else { + LOG("Syntax: stack-limit \n"); + return; + } + + last_focused->container->stack_limit_value = atoi(rest); + if (last_focused->container->stack_limit_value == 0) + last_focused->container->stack_limit = STACK_LIMIT_NONE; + + return; + } + /* Is it an ? */ if (STARTS_WITH(command, "exit")) { LOG("User issued exit-command, exiting without error.\n"); diff --git a/src/layout.c b/src/layout.c index b6d1688b..93d76414 100644 --- a/src/layout.c +++ b/src/layout.c @@ -391,6 +391,7 @@ void render_container(xcb_connection_t *conn, Container *container) { /* The size for each tab (width), necessary as a separate variable * because num_clients gets fixed to 1 in tabbed mode. */ int size_each = (num_clients == 0 ? container->width : container->width / num_clients); + int stack_lines = num_clients; /* Check if we need to remap our stack title window, it gets unmapped when the container is empty in src/handlers.c:unmap_notify() */ @@ -404,15 +405,21 @@ void render_container(xcb_connection_t *conn, Container *container) { /* By setting num_clients to 1 we force that the stack window will be only one line * high. The rest of the code is useful in both cases. */ LOG("tabbed mode, setting num_clients = 1\n"); - if (num_clients > 1) - num_clients = 1; + if (stack_lines > 1) + stack_lines = 1; + } + + if (container->stack_limit == STACK_LIMIT_COLS) { + stack_lines = ceil((float)num_clients / container->stack_limit_value); + } else if (container->stack_limit == STACK_LIMIT_ROWS) { + stack_lines = min(num_clients, container->stack_limit_value); } /* Check if we need to reconfigure our stack title window */ if (update_if_necessary(&(stack_win->rect.x), container->x) | update_if_necessary(&(stack_win->rect.y), container->y) | update_if_necessary(&(stack_win->rect.width), container->width) | - update_if_necessary(&(stack_win->rect.height), decoration_height * num_clients)) { + update_if_necessary(&(stack_win->rect.height), decoration_height * stack_lines)) { /* Configuration can happen in two slightly different ways: @@ -448,6 +455,22 @@ void render_container(xcb_connection_t *conn, Container *container) { /* Prepare the pixmap for usage */ cached_pixmap_prepare(conn, &(stack_win->pixmap)); + int current_row = 0, current_col = 0; + int wrap = 0; + + if (container->stack_limit == STACK_LIMIT_COLS) { + /* wrap stores the number of rows after which we will + * wrap to a new column. */ + wrap = ceil((float)num_clients / container->stack_limit_value); + } else if (container->stack_limit == STACK_LIMIT_ROWS) { + /* When limiting rows, the wrap variable serves a + * slightly different purpose: it holds the number of + * pixels which each client will get. This is constant + * during the following loop, so it saves us some + * divisions and ceil()ing. */ + wrap = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value)); + } + /* Render the decorations of all clients */ CIRCLEQ_FOREACH(client, &(container->clients), clients) { /* If the client is in fullscreen mode, it does not get reconfigured */ @@ -460,23 +483,66 @@ void render_container(xcb_connection_t *conn, Container *container) { * Note the bitwise OR instead of logical OR to force evaluation of all statements */ if (client->force_reconfigure | update_if_necessary(&(client->rect.x), container->x) | - update_if_necessary(&(client->rect.y), container->y + (decoration_height * num_clients)) | + update_if_necessary(&(client->rect.y), container->y + (decoration_height * stack_lines)) | update_if_necessary(&(client->rect.width), container->width) | - update_if_necessary(&(client->rect.height), container->height - (decoration_height * num_clients))) + update_if_necessary(&(client->rect.height), container->height - (decoration_height * stack_lines))) resize_client(conn, client); client->force_reconfigure = false; int offset_x = 0; int offset_y = 0; - if (container->mode == MODE_STACK) - offset_y = current_client++ * decoration_height; - else if (container->mode == MODE_TABBED) + if (container->mode == MODE_STACK) { + if (container->stack_limit == STACK_LIMIT_COLS) { + offset_x = current_col * (stack_win->rect.width / container->stack_limit_value); + offset_y = current_row * decoration_height; + current_row++; + if ((current_row % wrap) == 0) { + current_col++; + current_row = 0; + } + } else if (container->stack_limit == STACK_LIMIT_ROWS) { + offset_x = current_col * wrap; + offset_y = current_row * decoration_height; + current_row++; + if ((current_row % container->stack_limit_value) == 0) { + current_col++; + current_row = 0; + } + } else { + offset_y = current_client * decoration_height; + } + current_client++; + } else if (container->mode == MODE_TABBED) offset_x = current_client++ * size_each; decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc, offset_x, offset_y); } + /* Check if we need to fill one column because of an uneven + * amount of windows */ + if (container->mode == MODE_STACK) { + if (container->stack_limit == STACK_LIMIT_COLS && (current_col % 2) != 0) { + xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + + int offset_x = current_col * (stack_win->rect.width / container->stack_limit_value); + int offset_y = current_row * decoration_height; + xcb_rectangle_t rect = {offset_x, offset_y, + offset_x + container->width, + offset_y + decoration_height }; + xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect); + } else if (container->stack_limit == STACK_LIMIT_ROWS && (current_row % 2) != 0) { + xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + + int offset_x = current_col * wrap; + int offset_y = current_row * decoration_height; + xcb_rectangle_t rect = {offset_x, offset_y, + offset_x + container->width, + offset_y + decoration_height }; + xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect); + } + } + xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc, 0, 0, 0, 0, stack_win->rect.width, stack_win->rect.height); } diff --git a/src/mainx.c b/src/mainx.c index 329aae29..f8d12432 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -39,6 +39,7 @@ #include "data.h" #include "debug.h" #include "handlers.h" +#include "click.h" #include "i3.h" #include "layout.h" #include "queue.h"