refactor tree_move() into src/move.c, change config (!), change testcase

Due to lots of cases which were added and added to tree_move(), the function
was not really easy to understand. For this refactoring, I wrote tree_move()
from scratch, thinking about (hopefully) all cases. The testsuite still passes.

The move command also has different parameters now. Instead of the hard to
understand 'before v' stuff, we use 'move [left|right|up|down]'.
next
Michael Stapelberg 2011-02-14 23:05:20 +01:00
parent 28dd226259
commit 26a416e016
14 changed files with 302 additions and 250 deletions

View File

@ -4,7 +4,7 @@ include $(TOPDIR)/common.mk
# Depend on the object files of all source-files in src/*.c and on all header files
AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c
FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c
FILES:=src/ipc.c src/main.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c src/match.c src/xcursor.c src/resize.c src/sighandler.c src/move.c
FILES:=$(FILES:.c=.o)
HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))

View File

@ -66,10 +66,10 @@ bindsym Mod1+Down next v
bindsym Mod1+Up prev v
# Move
bindsym Mod1+Shift+n move before h
bindsym Mod1+Shift+r move before v
bindsym Mod1+Shift+t move after v
bindsym Mod1+Shift+d move after h
bindsym Mod1+Shift+n move left
bindsym Mod1+Shift+r move down
bindsym Mod1+Shift+t move up
bindsym Mod1+Shift+d move right
# alternatively, you can use the cursor keys:
bindsym Mod1+Shift+Left move before h

View File

@ -54,5 +54,6 @@
#include "xcursor.h"
#include "resize.h"
#include "sighandler.h"
#include "move.h"
#endif

View File

@ -42,6 +42,9 @@ Con *con_get_output(Con *con);
*/
Con *con_get_workspace(Con *con);
Con *con_parent_with_orientation(Con *con, orientation_t orientation);
/**
* Returns the first fullscreen node below this node.
*

15
include/move.h Normal file
View File

@ -0,0 +1,15 @@
/*
* vim:ts=4:sw=4:expandtab
*/
#ifndef _MOVE_H
#define _MOVE_H
/**
* Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,
* TOK_UP, TOK_DOWN from cmdparse.l)
*
*/
void tree_move(int direction);
#endif

View File

@ -65,13 +65,6 @@ void tree_close_con();
*/
void tree_next(char way, orientation_t orientation);
/**
* Moves the current container in the given way (next/previous) and given
* orientation (horizontal/vertical).
*
*/
void tree_move(char way, orientation_t orientation);
/**
* Closes the given container including all children
*

View File

@ -99,4 +99,6 @@ void workspace_map_clients(xcb_connection_t *conn, Workspace *ws);
*/
void workspace_update_urgent_flag(Con *ws);
void ws_force_orientation(Con *ws, orientation_t orientation);
#endif

View File

@ -112,8 +112,6 @@ up { return TOK_UP; }
down { return TOK_DOWN; }
left { return TOK_LEFT; }
right { return TOK_RIGHT; }
before { return TOK_BEFORE; }
after { return TOK_AFTER; }
resize { return TOK_RESIZE; }
shrink { return TOK_SHRINK; }
grow { return TOK_GROW; }

View File

@ -134,8 +134,6 @@ char *parse_cmd(const char *new) {
%token TOK_DOWN "down"
%token TOK_LEFT "left"
%token TOK_RIGHT "right"
%token TOK_AFTER "after"
%token TOK_BEFORE "before"
%token TOK_RESTORE "restore"
%token TOK_MARK "mark"
%token TOK_RESIZE "resize"
@ -527,12 +525,10 @@ level_direction:
;
move:
TOK_MOVE WHITESPACE before_after WHITESPACE direction
TOK_MOVE WHITESPACE direction
{
printf("moving: %s and %c\n", ($<number>3 == TOK_BEFORE ? "before" : "after"), $<chr>5);
/* TODO: change API for the next call, we need to convert in both directions while ideally
* we should not need any of both */
tree_move(($<number>3 == TOK_BEFORE ? 'p' : 'n'), ($<chr>5 == 'v' ? VERT : HORIZ));
printf("moving in direction %d\n", $<number>3);
tree_move($<number>3);
}
| TOK_MOVE WHITESPACE TOK_WORKSPACE WHITESPACE STR
{
@ -555,11 +551,6 @@ move:
}
;
before_after:
TOK_BEFORE { $<number>$ = TOK_BEFORE; }
| TOK_AFTER { $<number>$ = TOK_AFTER; }
;
restore:
TOK_RESTORE WHITESPACE STR
{

View File

@ -230,6 +230,24 @@ Con *con_get_workspace(Con *con) {
return result;
}
Con *con_parent_with_orientation(Con *con, orientation_t orientation) {
DLOG("Searching for parent of Con %p with orientation %d\n", con, orientation);
Con *parent = con->parent;
if (parent->type == CT_FLOATING_CON)
return NULL;
while (con_orientation(parent) != orientation) {
DLOG("Need to go one level further up\n");
parent = parent->parent;
/* Abort when we reach a floating con */
if (parent && parent->type == CT_FLOATING_CON)
parent = NULL;
if (parent == NULL)
break;
}
DLOG("Result: %p\n", parent);
return parent;
}
/*
* helper data structure for the breadth-first-search in
* con_get_fullscreen_con()
@ -742,15 +760,44 @@ void con_set_layout(Con *con, int layout) {
}
static void con_on_remove_child(Con *con) {
DLOG("on_remove_child\n");
/* Nothing to do for workspaces */
if (con->type == CT_WORKSPACE)
if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT || con->type == CT_ROOT) {
DLOG("not handling, type = %d\n", con->type);
return;
}
/* TODO: check if this container would swallow any other client and
* dont close it automatically. */
DLOG("on_remove_child\n");
if (con_num_children(con) == 0) {
int children = con_num_children(con);
if (children == 0) {
DLOG("Container empty, closing\n");
tree_close(con, false, false);
return;
}
/* If we did not close the container, check if we have only a single child left */
if (children == 1) {
Con *child = TAILQ_FIRST(&(con->nodes_head));
Con *parent = con->parent;
DLOG("Container has only one child, replacing con %p with child %p\n", con, child);
/* TODO: refactor it into con_swap */
TAILQ_REPLACE(&(parent->nodes_head), con, child, nodes);
TAILQ_REPLACE(&(parent->focus_head), con, child, focused);
if (focused == con)
focused = child;
child->parent = parent;
child->percent = 0.0;
con_fix_percent(parent);
con->parent = NULL;
x_con_kill(con);
free(con->name);
TAILQ_REMOVE(&all_cons, con, all_cons);
free(con);
return;
}
}

174
src/move.c Normal file
View File

@ -0,0 +1,174 @@
/*
* vim:ts=4:sw=4:expandtab
*/
#include "all.h"
#include "cmdparse.tab.h"
typedef enum { BEFORE, AFTER } position_t;
/*
* This function detaches 'con' from its parent and inserts it either before or
* after 'target'.
*
*/
static void insert_con_into(Con *con, Con *target, position_t position) {
Con *parent = target->parent;
/* We need to preserve the old con->parent. While it might still be used to
* insert the entry before/after it, we call the on_remove_child callback
* afterwards which might then close the con if it is empty. */
Con *old_parent = con->parent;
con_detach(con);
con_fix_percent(con->parent);
con->parent = parent;
if (position == BEFORE) {
TAILQ_INSERT_BEFORE(target, con, nodes);
TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
} else if (position == AFTER) {
TAILQ_INSERT_AFTER(&(parent->nodes_head), target, con, nodes);
TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
}
/* Pretend the con was just opened with regards to size percent values.
* Since the con is moved to a completely different con, the old value
* does not make sense anyways. */
con->percent = 0.0;
con_fix_percent(parent);
CALL(old_parent, on_remove_child);
}
/*
* This function detaches 'con' from its parent and inserts it at the given
* workspace.
*
*/
static void attach_to_workspace(Con *con, Con *ws) {
con_detach(con);
con_fix_percent(con->parent);
CALL(con->parent, on_remove_child);
con->parent = ws;
TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
/* Pretend the con was just opened with regards to size percent values.
* Since the con is moved to a completely different con, the old value
* does not make sense anyways. */
con->percent = 0.0;
con_fix_percent(ws);
}
/*
* Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,
* TOK_UP, TOK_DOWN from cmdparse.l)
*
*/
void tree_move(int direction) {
DLOG("Moving in direction %d\n", direction);
/* 1: get the first parent with the same orientation */
Con *con = focused;
if (con->type == CT_WORKSPACE) {
DLOG("Not moving workspace\n");
return;
}
if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) {
DLOG("This is the only con on this workspace, not doing anything\n");
return;
}
orientation_t o = (direction == TOK_LEFT || direction == TOK_RIGHT ? HORIZ : VERT);
Con *same_orientation = con_parent_with_orientation(con, o);
/* There is no parent container with the same orientation */
if (!same_orientation) {
if (con_is_floating(con)) {
/* this is a floating con, we just disable floating */
floating_disable(con, true);
return;
}
if (con_inside_floating(con)) {
/* 'con' should be moved out of a floating container */
DLOG("Inside floating, moving to workspace\n");
attach_to_workspace(con, con_get_workspace(con));
goto end;
}
DLOG("Force-changing orientation\n");
ws_force_orientation(con_get_workspace(con), o);
same_orientation = con_parent_with_orientation(con, o);
}
/* easy case: the move is within this container */
if (same_orientation == con->parent) {
DLOG("We are in the same container\n");
Con *swap;
/* TODO: TAILQ_SWAP? */
if (direction == TOK_LEFT || direction == TOK_UP) {
if (!(swap = TAILQ_PREV(con, nodes_head, nodes)))
return;
if (!con_is_leaf(swap)) {
insert_con_into(con, con_descend_focused(swap), AFTER);
goto end;
}
/* the container right of the current one is a normal one. */
con_detach(con);
TAILQ_INSERT_BEFORE(swap, con, nodes);
TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused);
} else {
if (!(swap = TAILQ_NEXT(con, nodes)))
return;
if (!con_is_leaf(swap)) {
insert_con_into(con, con_descend_focused(swap), AFTER);
goto end;
}
con_detach(con);
TAILQ_INSERT_AFTER(&(swap->parent->nodes_head), swap, con, nodes);
TAILQ_INSERT_HEAD(&(swap->parent->focus_head), con, focused);
}
DLOG("Swapped.\n");
return;
}
/* this time, we have to move to another container */
/* This is the container *above* 'con' which is inside 'same_orientation' */
Con *above = con;
while (above->parent != same_orientation)
above = above->parent;
DLOG("above = %p\n", above);
Con *next;
position_t position;
if (direction == TOK_UP || direction == TOK_LEFT) {
position = BEFORE;
next = TAILQ_PREV(above, nodes_head, nodes);
} else if (direction == TOK_DOWN || direction == TOK_RIGHT) {
position = AFTER;
next = TAILQ_NEXT(above, nodes);
}
/* special case: there is a split container in the direction we are moving
* to, so descend and append */
if (next && !con_is_leaf(next))
insert_con_into(con, con_descend_focused(next), AFTER);
else
insert_con_into(con, above, position);
end:
/* We need to call con_focus() to fix the focus stack "above" the container
* we just inserted the focused container into (otherwise, the parent
* container(s) would still point to the old container(s)). */
con_focus(con);
tree_flatten(croot);
}

View File

@ -365,213 +365,6 @@ void tree_next(char way, orientation_t orientation) {
con_focus(con_descend_focused(next));
}
/*
* Moves the current container in the given way (next/previous) and given
* orientation (horizontal/vertical).
*
*/
void tree_move(char way, orientation_t orientation) {
/* 1: get the first parent with the same orientation */
Con *con = focused;
Con *parent = con->parent;
Con *old_parent = parent;
if (con->type == CT_WORKSPACE)
return;
DLOG("con = %p / %s\n", con, con->name);
bool level_changed = false;
while (con_orientation(parent) != orientation) {
DLOG("need to go one level further up\n");
/* If the current parent is an output, we are at a workspace
* and the orientation still does not match. In this case, we split the
* workspace to have the same look & feel as in older i3 releases. */
if (parent->type == CT_WORKSPACE) {
DLOG("Arrived at workspace (%p / %s)\n", parent, parent->name);
/* In case of moving a window out of a floating con, there might be
* not a single tiling container. Makes no sense to split then, so
* just use the workspace as target */
if (TAILQ_EMPTY(&(parent->nodes_head)))
break;
/* Check if there are any other cons at all. If not, there is no
* point in creating a new split con and changing workspace
* orientation. Instead, the operation is a no-op. */
Con *child;
bool other_container = false;
TAILQ_FOREACH(child, &(parent->nodes_head), nodes)
if (child != con)
other_container = true;
if (!other_container) {
DLOG("No other container found, we are not creating this split container.\n");
return;
}
/* 1: create a new split container */
Con *new = con_new(NULL);
new->parent = parent;
/* 2: copy layout and orientation from workspace */
new->layout = parent->layout;
new->orientation = parent->orientation;
Con *old_focused = TAILQ_FIRST(&(parent->focus_head));
if (old_focused == TAILQ_END(&(parent->focus_head)))
old_focused = NULL;
/* 3: move the existing cons of this workspace below the new con */
DLOG("Moving cons\n");
while (!TAILQ_EMPTY(&(parent->nodes_head))) {
child = TAILQ_FIRST(&(parent->nodes_head));
con_detach(child);
con_attach(child, new, true);
}
/* 4: switch workspace orientation */
parent->orientation = orientation;
/* 5: attach the new split container to the workspace */
DLOG("Attaching new split to ws\n");
con_attach(new, parent, false);
/* 6: fix the percentages */
con_fix_percent(parent);
if (old_focused)
con_focus(old_focused);
level_changed = true;
break;
}
parent = parent->parent;
level_changed = true;
}
/* If we have no tiling cons (when moving a window out of a floating con to
* an otherwise empty workspace for example), we just attach the window to
* the workspace. */
bool fix_percent = false;
if (TAILQ_EMPTY(&(parent->nodes_head))) {
con_detach(con);
con_fix_percent(con->parent);
con->parent = parent;
fix_percent = true;
TAILQ_INSERT_HEAD(&(parent->nodes_head), con, nodes);
TAILQ_INSERT_HEAD(&(parent->focus_head), con, focused);
} else {
Con *current = NULL, *loop;
/* Get the first tiling container in focus stack */
TAILQ_FOREACH(loop, &(parent->focus_head), focused) {
if (loop->type == CT_FLOATING_CON)
continue;
current = loop;
break;
}
assert(current != TAILQ_END(&(parent->focus_head)));
/* 2: chose next (or previous) */
Con *next = current;
if (way == 'n') {
LOG("i would insert it after %p / %s\n", next, next->name);
/* Have a look at the next container: If there is no next container or
* if it is a leaf node, we move the con one left to it. However,
* for split containers, we descend into it. */
next = TAILQ_NEXT(next, nodes);
if (next == TAILQ_END(&(next->parent->nodes_head))) {
if (con == current)
return;
next = current;
} else {
if (level_changed && con_is_leaf(next)) {
next = current;
} else {
/* if this is a split container, we need to go down */
next = con_descend_focused(next);
}
}
con_detach(con);
if (con->parent != next->parent) {
con_fix_percent(con->parent);
con->parent = next->parent;
fix_percent = true;
}
CALL(con->parent, on_remove_child);
TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, con, nodes);
TAILQ_INSERT_HEAD(&(next->parent->focus_head), con, focused);
/* TODO: dont influence focus handling? */
} else {
LOG("i would insert it before %p / %s\n", current, current->name);
bool gone_down = false;
next = TAILQ_PREV(next, nodes_head, nodes);
if (next == TAILQ_END(&(next->parent->nodes_head))) {
if (con == current) {
DLOG("Cannot move, no other container in that direction\n");
return;
}
next = current;
} else {
if (level_changed && con_is_leaf(next)) {
next = current;
} else {
/* if this is a split container, we need to go down */
while (!TAILQ_EMPTY(&(next->focus_head))) {
gone_down = true;
next = TAILQ_FIRST(&(next->focus_head));
}
}
}
DLOG("detaching con = %p / %s, next = %p / %s\n",
con, con->name, next, next->name);
con_detach(con);
if (con->parent != next->parent) {
DLOG("different parents. new parent = %p / %s\n", next->parent, next->parent->name);
con_fix_percent(con->parent);
con->parent = next->parent;
fix_percent = true;
}
/* After going down in the tree, we insert the container *after*
* the currently focused one even though the command used "before".
* This is to keep the user experience clear, since the before/after
* only signifies the direction of the movement on top-level */
if (gone_down)
TAILQ_INSERT_AFTER(&(next->parent->nodes_head), next, con, nodes);
else TAILQ_INSERT_BEFORE(next, con, nodes);
TAILQ_INSERT_HEAD(&(next->parent->focus_head), con, focused);
/* TODO: dont influence focus handling? */
}
}
/* fix the percentages in the container we moved to */
if (fix_percent) {
int children = con_num_children(con->parent);
if (children == 1) {
con->percent = 1.0;
} else {
con->percent = 1.0 / (children - 1);
con_fix_percent(con->parent);
}
}
/* We need to call con_focus() to fix the focus stack "above" the container
* we just inserted the focused container into (otherwise, the parent
* container(s) would still point to the old container(s)). */
con_focus(con);
/* fix the percentages in the container we moved from */
if (level_changed)
con_fix_percent(old_parent);
CALL(old_parent, on_remove_child);
tree_flatten(croot);
}
/*
* tree_flatten() removes pairs of redundant split containers, e.g.:
* [workspace, horizontal]

View File

@ -487,3 +487,36 @@ void workspace_update_urgent_flag(Con *ws) {
if (old_flag != ws->urgent)
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
}
void ws_force_orientation(Con *ws, orientation_t orientation) {
/* 1: create a new split container */
Con *split = con_new(NULL);
split->parent = ws;
/* 2: copy layout and orientation from workspace */
split->layout = ws->layout;
split->orientation = ws->orientation;
Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
/* 3: move the existing cons of this workspace below the new con */
DLOG("Moving cons\n");
while (!TAILQ_EMPTY(&(ws->nodes_head))) {
Con *child = TAILQ_FIRST(&(ws->nodes_head));
con_detach(child);
con_attach(child, split, true);
}
/* 4: switch workspace orientation */
ws->orientation = orientation;
/* 5: attach the new split container to the workspace */
DLOG("Attaching new split to ws\n");
con_attach(split, ws, false);
/* 6: fix the percentages */
con_fix_percent(ws);
if (old_focused)
con_focus(old_focused);
}

View File

@ -13,13 +13,13 @@ use X11::XCB qw(:all);
my $i3 = i3("/tmp/nestedcons");
my $tmp = get_unused_workspace();
$i3->command("workspace $tmp")->recv;
cmd "workspace $tmp";
######################################################################
# 1) move a container which cannot be moved
######################################################################
$i3->command('open')->recv;
cmd 'open';
my $old_content = get_ws_content($tmp);
is(@{$old_content}, 1, 'one container on this workspace');
@ -46,22 +46,22 @@ my $second = $content->[1]->{id};
is($content->[0]->{id}, $first, 'first container unmodified');
# Move the second container before the first one (→ swap them)
$i3->command('move before h')->recv;
$i3->command('move left')->recv;
$content = get_ws_content($tmp);
is($content->[0]->{id}, $second, 'first container modified');
# We should not be able to move any further
$i3->command('move before h')->recv;
$i3->command('move left')->recv;
$content = get_ws_content($tmp);
is($content->[0]->{id}, $second, 'first container unmodified');
# Now move in the other direction
$i3->command('move after h')->recv;
$i3->command('move right')->recv;
$content = get_ws_content($tmp);
is($content->[0]->{id}, $first, 'first container modified');
# We should not be able to move any further
$i3->command('move after h')->recv;
$i3->command('move right')->recv;
$content = get_ws_content($tmp);
is($content->[0]->{id}, $first, 'first container unmodified');
@ -84,7 +84,7 @@ $content = get_ws_content($tmp);
is(@{$content}, 3, 'three containers on this workspace');
my $third = $content->[2]->{id};
$i3->command('move before h')->recv;
$i3->command('move left')->recv;
$content = get_ws_content($tmp);
is(@{$content}, 2, 'only two containers on this workspace');
my $nodes = $content->[1]->{nodes};
@ -95,19 +95,21 @@ is($nodes->[1]->{id}, $third, 'third container on bottom');
# move it inside the split container
######################################################################
$i3->command('move before v')->recv;
$i3->command('move up')->recv;
$nodes = get_ws_content($tmp)->[1]->{nodes};
is($nodes->[0]->{id}, $third, 'third container on top');
is($nodes->[1]->{id}, $second, 'second container on bottom');
# move it outside again
$i3->command('move before h')->recv;
$i3->command('move left')->recv;
$content = get_ws_content($tmp);
is(@{$content}, 3, 'three nodes on this workspace');
$i3->command('move after h')->recv;
# due to automatic flattening/cleanup, the remaining split container
# will be replaced by the con itself, so we will still have 3 nodes
$i3->command('move right')->recv;
$content = get_ws_content($tmp);
is(@{$content}, 2, 'two nodes on this workspace');
is(@{$content}, 3, 'two nodes on this workspace');
######################################################################
# 4) We create two v-split containers on the workspace, then we move
@ -116,7 +118,7 @@ is(@{$content}, 2, 'two nodes on this workspace');
######################################################################
my $otmp = get_unused_workspace();
$i3->command("workspace $otmp")->recv;
cmd "workspace $otmp";
$i3->command("open")->recv;
$i3->command("open")->recv;
@ -125,9 +127,9 @@ $i3->command("open")->recv;
$i3->command("prev h")->recv;
$i3->command("split v")->recv;
$i3->command("open")->recv;
$i3->command("move after h")->recv;
$i3->command("move right")->recv;
$i3->command("prev h")->recv;
$i3->command("move after h")->recv;
$i3->command("move right")->recv;
$content = get_ws_content($otmp);
is(@{$content}, 1, 'only one nodes on this workspace');