]> git.sur5r.net Git - i3/i3/commitdiff
layout toggle: take any combination of layouts as arguments (#2649) 2649/head
authors3rb31 <s3rb31@mail.ru>
Thu, 19 Jan 2017 19:49:56 +0000 (20:49 +0100)
committers3rb31 <s3rb31@mail.ru>
Thu, 23 Feb 2017 23:34:18 +0000 (00:34 +0100)
With this PR the 'layout toggle' command can be passed any
combination of valid layout keywords as arguments. They will
be activated one after another each time you issue the command,
advancing from left to right always selecting the layout after
the currently active layout or the leftmost layout if the active
layout is not in the argument list.

This PR also incorporates the feature request from #2476.

docs/userguide
parser-specs/commands.spec
src/con.c
src/util.c
testcases/t/192-layout.t

index 6dc2241b4d411c48ea2c14c8a32382160ab26d01..21d0862521edb5e698172a9f13773b57a0a4b84a 100644 (file)
@@ -1802,7 +1802,8 @@ The +toggle+ option will toggle the orientation of the split container if it
 contains a single window. Otherwise it makes the current window a split
 container with opposite orientation compared to the parent container.
 Use +layout toggle split+ to change the layout of any split container from
-splitv to splith or vice-versa.
+splitv to splith or vice-versa. You can also define a custom sequence of layouts
+to cycle through with +layout toggle+, see <<manipulating_layout>>.
 
 *Syntax*:
 --------------------------------
@@ -1822,6 +1823,11 @@ Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+
 or +layout splith+ to change the current container layout to splith/splitv,
 stacking, tabbed layout, splitv or splith, respectively.
 
+Specify up to four layouts after +layout toggle+ to cycle through them. Every
+time the command is executed, the layout specified after the currently active
+one will be applied. If the currently active layout is not in the list, the
+first layout in the list will be activated.
+
 To make the current window (!) fullscreen, use +fullscreen enable+ (or
 +fullscreen enable global+ for the global mode), to leave either fullscreen
 mode use +fullscreen disable+, and to toggle between these two states use
@@ -1834,6 +1840,7 @@ enable+ respectively +floating disable+ (or +floating toggle+):
 --------------------------------------------
 layout default|tabbed|stacking|splitv|splith
 layout toggle [split|all]
+layout toggle [split|tabbed|stacking|splitv|splith] [split|tabbed|stacking|splitv|splith]…
 --------------------------------------------
 
 *Examples*:
@@ -1848,6 +1855,15 @@ bindsym $mod+x layout toggle
 # Toggle between stacking/tabbed/splith/splitv:
 bindsym $mod+x layout toggle all
 
+# Toggle between stacking/tabbed/splith:
+bindsym $mod+x layout toggle stacking tabbed splith
+
+# Toggle between splitv/tabbed
+bindsym $mod+x layout toggle splitv tabbed
+
+# Toggle between last split layout/tabbed/stacking
+bindsym $mod+x layout toggle split tabbed stacking
+
 # Toggle fullscreen
 bindsym $mod+f fullscreen toggle
 
index d4b3dbc61b066c9d57807719d02f93e1f8d1b5c2..542ff79895c3b1d257b6468e5eee6764f6990240 100644 (file)
@@ -110,7 +110,7 @@ state LAYOUT:
 state LAYOUT_TOGGLE:
   end
       -> call cmd_layout_toggle($toggle_mode)
-  toggle_mode = 'split', 'all'
+  toggle_mode = string
       -> call cmd_layout_toggle($toggle_mode)
 
 # append_layout <path>
index 40924a73d691a703099e66a4f0c32a3e09fa81c4..b3f193e6b23f11dcf16579827e98b31679ea3ff2 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -1719,28 +1719,64 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
         parent = con->parent;
     DLOG("con_toggle_layout(%p, %s), parent = %p\n", con, toggle_mode, parent);
 
-    if (strcmp(toggle_mode, "split") == 0) {
-        /* Toggle between splits. When the current layout is not a split
-         * layout, we just switch back to last_split_layout. Otherwise, we
-         * change to the opposite split layout. */
-        if (parent->layout != L_SPLITH && parent->layout != L_SPLITV)
-            con_set_layout(con, parent->last_split_layout);
-        else {
-            if (parent->layout == L_SPLITH)
-                con_set_layout(con, L_SPLITV);
-            else
-                con_set_layout(con, L_SPLITH);
+    const char delim[] = " ";
+
+    if (strcasecmp(toggle_mode, "split") == 0 || strstr(toggle_mode, delim)) {
+        /* L_DEFAULT is used as a placeholder value to distinguish if
+         * the first layout has already been saved. (it can never be L_DEFAULT) */
+        layout_t new_layout = L_DEFAULT;
+        bool current_layout_found = false;
+        char *tm_dup = sstrdup(toggle_mode);
+        char *cur_tok = strtok(tm_dup, delim);
+
+        for (layout_t layout; cur_tok != NULL; cur_tok = strtok(NULL, delim)) {
+            if (strcasecmp(cur_tok, "split") == 0) {
+                /* Toggle between splits. When the current layout is not a split
+                 * layout, we just switch back to last_split_layout. Otherwise, we
+                 * change to the opposite split layout. */
+                if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) {
+                    layout = parent->last_split_layout;
+                } else {
+                    layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH;
+                }
+            } else {
+                bool success = layout_from_name(cur_tok, &layout);
+                if (!success || layout == L_DEFAULT) {
+                    ELOG("The token '%s' was not recognized and has been skipped.\n", cur_tok);
+                    continue;
+                }
+            }
+
+            /* If none of the specified layouts match the current,
+             * fall back to the first layout in the list */
+            if (new_layout == L_DEFAULT) {
+                new_layout = layout;
+            }
+
+            /* We found the active layout in the last iteration, so
+             * now let's activate the current layout (next in list) */
+            if (current_layout_found) {
+                new_layout = layout;
+                free(tm_dup);
+                break;
+            }
+
+            if (parent->layout == layout) {
+                current_layout_found = true;
+            }
         }
-    } else {
+
+        con_set_layout(con, new_layout);
+    } else if (strcasecmp(toggle_mode, "all") == 0 || strcasecmp(toggle_mode, "default") == 0) {
         if (parent->layout == L_STACKED)
             con_set_layout(con, L_TABBED);
         else if (parent->layout == L_TABBED) {
-            if (strcmp(toggle_mode, "all") == 0)
+            if (strcasecmp(toggle_mode, "all") == 0)
                 con_set_layout(con, L_SPLITH);
             else
                 con_set_layout(con, parent->last_split_layout);
         } else if (parent->layout == L_SPLITH || parent->layout == L_SPLITV) {
-            if (strcmp(toggle_mode, "all") == 0) {
+            if (strcasecmp(toggle_mode, "all") == 0) {
                 /* When toggling through all modes, we toggle between
                  * splith/splitv, whereas normally we just directly jump to
                  * stacked. */
index b6f45fdef56ff710f1f96d60222d12a47eaca223..0289ded91f6a5a8a4a8dc1b6dbd9ae14f3de1465 100644 (file)
@@ -66,7 +66,7 @@ __attribute__((pure)) bool name_is_digits(const char *name) {
     return true;
 }
 
-/**
+/*
  * Set 'out' to the layout_t value for the given layout. The function
  * returns true on success or false if the passed string is not a valid
  * layout name.
@@ -75,20 +75,23 @@ __attribute__((pure)) bool name_is_digits(const char *name) {
 bool layout_from_name(const char *layout_str, layout_t *out) {
     if (strcmp(layout_str, "default") == 0) {
         *out = L_DEFAULT;
+        return true;
     } else if (strcasecmp(layout_str, "stacked") == 0 ||
                strcasecmp(layout_str, "stacking") == 0) {
         *out = L_STACKED;
+        return true;
     } else if (strcasecmp(layout_str, "tabbed") == 0) {
         *out = L_TABBED;
+        return true;
     } else if (strcasecmp(layout_str, "splitv") == 0) {
         *out = L_SPLITV;
+        return true;
     } else if (strcasecmp(layout_str, "splith") == 0) {
         *out = L_SPLITH;
-    } else {
-        return false;
+        return true;
     }
 
-    return true;
+    return false;
 }
 
 /*
index 6fd6eae8e89e7151cf3078894aa9dcec0b6e40f8..1d406fc64f0411f0ffd73a72f445f12169b739a5 100644 (file)
@@ -95,4 +95,72 @@ cmd 'layout toggle all';
 ($nodes, $focus) = get_ws_content($tmp);
 is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
 
+cmd 'layout toggle splith splitv';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle splith splitv';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle stacked splitv tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle stacking splitv tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle stacking splitv tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle splitv i stacking tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle stacked';
+($nodes, $focus) = get_ws_content($tmp);
+# this is correct if it does nothing
+is($nodes->[1]->{layout}, 'stacked', 'layout now tabbed');
+
+cmd 'layout toggle tabbed stacked';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now stacked');
+
+# obsoletes 'split' ;)
+cmd 'layout toggle splith splitv';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+# nonsense but works expectedly
+cmd 'layout toggle split split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle split split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+# testing with arbitrary length and garbage
+cmd 'layout toggle stacking splith tabbed splitv stacking';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle stacking splith garbage tabbed splitv stacking';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle stacking splith garbage tabbed splitv stacking';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle splitv splith garbage splitv tabbed stacking splitv';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle splitv garbage    tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
 done_testing;