]> git.sur5r.net Git - i3/i3/commitdiff
Implement stack-limit for further defining how stack windows should look
authorMichael Stapelberg <michael@stapelberg.de>
Tue, 22 Sep 2009 16:07:59 +0000 (18:07 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Tue, 22 Sep 2009 16:07:59 +0000 (18:07 +0200)
Using this command, you can limit the amount of columns or rows for
a stacking container. This allows for better usage of screen estate
when using stacking containers with many clients.

Examples:
  i3-msg "stack-limit cols 2"
You will now have a stack window which has two columns of windows.

include/data.h
src/click.c
src/commands.c
src/layout.c
src/mainx.c

index 0f0a243daac7690194a5934cbb422555f9919745..3f35c595c08e57f28bea9db17ecb999c316fd344 100644 (file)
@@ -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;
 };
 
index ac3f94409bff652489fc5e1526ecd95244b249da..4b27d1be4179eeb0a4f712b4471be266d9cc8ec9 100644 (file)
@@ -17,6 +17,7 @@
 #include <stdlib.h>
 #include <time.h>
 #include <stdbool.h>
+#include <math.h>
 
 #include <xcb/xcb.h>
 #include <xcb/xcb_atom.h>
 #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.
  */
 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;
 }
 
 /*
index 5ca92b3185ee02f135296920804d26f25ce9c30b..191eb5bcb33e6a1695d5c5df3ea8ae7ebe48b29d 100644 (file)
@@ -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 <cols|rows> <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 <exit>? */
         if (STARTS_WITH(command, "exit")) {
                 LOG("User issued exit-command, exiting without error.\n");
index b6d1688b4f9f935f0600bc57d1feebd48b6bd18c..93d76414691e8dafb110c45eae082d961f99ad99 100644 (file)
@@ -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);
         }
index 329aae2920e3ec4a2defdb17522ec86736191de3..f8d12432930b7b875f346b968253e9b65cfca2c2 100644 (file)
@@ -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"