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]'.
This commit is contained in:
parent
28dd226259
commit
26a416e016
2
Makefile
2
Makefile
|
@ -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
|
# 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
|
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)
|
FILES:=$(FILES:.c=.o)
|
||||||
HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
|
HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
|
||||||
|
|
||||||
|
|
|
@ -66,10 +66,10 @@ bindsym Mod1+Down next v
|
||||||
bindsym Mod1+Up prev v
|
bindsym Mod1+Up prev v
|
||||||
|
|
||||||
# Move
|
# Move
|
||||||
bindsym Mod1+Shift+n move before h
|
bindsym Mod1+Shift+n move left
|
||||||
bindsym Mod1+Shift+r move before v
|
bindsym Mod1+Shift+r move down
|
||||||
bindsym Mod1+Shift+t move after v
|
bindsym Mod1+Shift+t move up
|
||||||
bindsym Mod1+Shift+d move after h
|
bindsym Mod1+Shift+d move right
|
||||||
|
|
||||||
# alternatively, you can use the cursor keys:
|
# alternatively, you can use the cursor keys:
|
||||||
bindsym Mod1+Shift+Left move before h
|
bindsym Mod1+Shift+Left move before h
|
||||||
|
|
|
@ -54,5 +54,6 @@
|
||||||
#include "xcursor.h"
|
#include "xcursor.h"
|
||||||
#include "resize.h"
|
#include "resize.h"
|
||||||
#include "sighandler.h"
|
#include "sighandler.h"
|
||||||
|
#include "move.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -42,6 +42,9 @@ Con *con_get_output(Con *con);
|
||||||
*/
|
*/
|
||||||
Con *con_get_workspace(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.
|
* Returns the first fullscreen node below this node.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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
|
|
@ -65,13 +65,6 @@ void tree_close_con();
|
||||||
*/
|
*/
|
||||||
void tree_next(char way, orientation_t orientation);
|
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
|
* Closes the given container including all children
|
||||||
*
|
*
|
||||||
|
|
|
@ -99,4 +99,6 @@ void workspace_map_clients(xcb_connection_t *conn, Workspace *ws);
|
||||||
*/
|
*/
|
||||||
void workspace_update_urgent_flag(Con *ws);
|
void workspace_update_urgent_flag(Con *ws);
|
||||||
|
|
||||||
|
void ws_force_orientation(Con *ws, orientation_t orientation);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -112,8 +112,6 @@ up { return TOK_UP; }
|
||||||
down { return TOK_DOWN; }
|
down { return TOK_DOWN; }
|
||||||
left { return TOK_LEFT; }
|
left { return TOK_LEFT; }
|
||||||
right { return TOK_RIGHT; }
|
right { return TOK_RIGHT; }
|
||||||
before { return TOK_BEFORE; }
|
|
||||||
after { return TOK_AFTER; }
|
|
||||||
resize { return TOK_RESIZE; }
|
resize { return TOK_RESIZE; }
|
||||||
shrink { return TOK_SHRINK; }
|
shrink { return TOK_SHRINK; }
|
||||||
grow { return TOK_GROW; }
|
grow { return TOK_GROW; }
|
||||||
|
|
|
@ -134,8 +134,6 @@ char *parse_cmd(const char *new) {
|
||||||
%token TOK_DOWN "down"
|
%token TOK_DOWN "down"
|
||||||
%token TOK_LEFT "left"
|
%token TOK_LEFT "left"
|
||||||
%token TOK_RIGHT "right"
|
%token TOK_RIGHT "right"
|
||||||
%token TOK_AFTER "after"
|
|
||||||
%token TOK_BEFORE "before"
|
|
||||||
%token TOK_RESTORE "restore"
|
%token TOK_RESTORE "restore"
|
||||||
%token TOK_MARK "mark"
|
%token TOK_MARK "mark"
|
||||||
%token TOK_RESIZE "resize"
|
%token TOK_RESIZE "resize"
|
||||||
|
@ -527,12 +525,10 @@ level_direction:
|
||||||
;
|
;
|
||||||
|
|
||||||
move:
|
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);
|
printf("moving in direction %d\n", $<number>3);
|
||||||
/* TODO: change API for the next call, we need to convert in both directions while ideally
|
tree_move($<number>3);
|
||||||
* we should not need any of both */
|
|
||||||
tree_move(($<number>3 == TOK_BEFORE ? 'p' : 'n'), ($<chr>5 == 'v' ? VERT : HORIZ));
|
|
||||||
}
|
}
|
||||||
| TOK_MOVE WHITESPACE TOK_WORKSPACE WHITESPACE STR
|
| 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:
|
restore:
|
||||||
TOK_RESTORE WHITESPACE STR
|
TOK_RESTORE WHITESPACE STR
|
||||||
{
|
{
|
||||||
|
|
53
src/con.c
53
src/con.c
|
@ -230,6 +230,24 @@ Con *con_get_workspace(Con *con) {
|
||||||
return result;
|
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
|
* helper data structure for the breadth-first-search in
|
||||||
* con_get_fullscreen_con()
|
* con_get_fullscreen_con()
|
||||||
|
@ -742,15 +760,44 @@ void con_set_layout(Con *con, int layout) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void con_on_remove_child(Con *con) {
|
static void con_on_remove_child(Con *con) {
|
||||||
|
DLOG("on_remove_child\n");
|
||||||
|
|
||||||
/* Nothing to do for workspaces */
|
/* 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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO: check if this container would swallow any other client and
|
/* TODO: check if this container would swallow any other client and
|
||||||
* don’t close it automatically. */
|
* don’t close it automatically. */
|
||||||
DLOG("on_remove_child\n");
|
int children = con_num_children(con);
|
||||||
if (con_num_children(con) == 0) {
|
if (children == 0) {
|
||||||
DLOG("Container empty, closing\n");
|
DLOG("Container empty, closing\n");
|
||||||
tree_close(con, false, false);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
207
src/tree.c
207
src/tree.c
|
@ -365,213 +365,6 @@ void tree_next(char way, orientation_t orientation) {
|
||||||
con_focus(con_descend_focused(next));
|
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: don’t 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: don’t 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.:
|
* tree_flatten() removes pairs of redundant split containers, e.g.:
|
||||||
* [workspace, horizontal]
|
* [workspace, horizontal]
|
||||||
|
|
|
@ -487,3 +487,36 @@ void workspace_update_urgent_flag(Con *ws) {
|
||||||
if (old_flag != ws->urgent)
|
if (old_flag != ws->urgent)
|
||||||
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"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);
|
||||||
|
}
|
||||||
|
|
|
@ -13,13 +13,13 @@ use X11::XCB qw(:all);
|
||||||
my $i3 = i3("/tmp/nestedcons");
|
my $i3 = i3("/tmp/nestedcons");
|
||||||
|
|
||||||
my $tmp = get_unused_workspace();
|
my $tmp = get_unused_workspace();
|
||||||
$i3->command("workspace $tmp")->recv;
|
cmd "workspace $tmp";
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# 1) move a container which cannot be moved
|
# 1) move a container which cannot be moved
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
$i3->command('open')->recv;
|
cmd 'open';
|
||||||
|
|
||||||
my $old_content = get_ws_content($tmp);
|
my $old_content = get_ws_content($tmp);
|
||||||
is(@{$old_content}, 1, 'one container on this workspace');
|
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');
|
is($content->[0]->{id}, $first, 'first container unmodified');
|
||||||
|
|
||||||
# Move the second container before the first one (→ swap them)
|
# 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);
|
$content = get_ws_content($tmp);
|
||||||
is($content->[0]->{id}, $second, 'first container modified');
|
is($content->[0]->{id}, $second, 'first container modified');
|
||||||
|
|
||||||
# We should not be able to move any further
|
# 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);
|
$content = get_ws_content($tmp);
|
||||||
is($content->[0]->{id}, $second, 'first container unmodified');
|
is($content->[0]->{id}, $second, 'first container unmodified');
|
||||||
|
|
||||||
# Now move in the other direction
|
# Now move in the other direction
|
||||||
$i3->command('move after h')->recv;
|
$i3->command('move right')->recv;
|
||||||
$content = get_ws_content($tmp);
|
$content = get_ws_content($tmp);
|
||||||
is($content->[0]->{id}, $first, 'first container modified');
|
is($content->[0]->{id}, $first, 'first container modified');
|
||||||
|
|
||||||
# We should not be able to move any further
|
# 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);
|
$content = get_ws_content($tmp);
|
||||||
is($content->[0]->{id}, $first, 'first container unmodified');
|
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');
|
is(@{$content}, 3, 'three containers on this workspace');
|
||||||
my $third = $content->[2]->{id};
|
my $third = $content->[2]->{id};
|
||||||
|
|
||||||
$i3->command('move before h')->recv;
|
$i3->command('move left')->recv;
|
||||||
$content = get_ws_content($tmp);
|
$content = get_ws_content($tmp);
|
||||||
is(@{$content}, 2, 'only two containers on this workspace');
|
is(@{$content}, 2, 'only two containers on this workspace');
|
||||||
my $nodes = $content->[1]->{nodes};
|
my $nodes = $content->[1]->{nodes};
|
||||||
|
@ -95,19 +95,21 @@ is($nodes->[1]->{id}, $third, 'third container on bottom');
|
||||||
# move it inside the split container
|
# move it inside the split container
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
$i3->command('move before v')->recv;
|
$i3->command('move up')->recv;
|
||||||
$nodes = get_ws_content($tmp)->[1]->{nodes};
|
$nodes = get_ws_content($tmp)->[1]->{nodes};
|
||||||
is($nodes->[0]->{id}, $third, 'third container on top');
|
is($nodes->[0]->{id}, $third, 'third container on top');
|
||||||
is($nodes->[1]->{id}, $second, 'second container on bottom');
|
is($nodes->[1]->{id}, $second, 'second container on bottom');
|
||||||
|
|
||||||
# move it outside again
|
# move it outside again
|
||||||
$i3->command('move before h')->recv;
|
$i3->command('move left')->recv;
|
||||||
$content = get_ws_content($tmp);
|
$content = get_ws_content($tmp);
|
||||||
is(@{$content}, 3, 'three nodes on this workspace');
|
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);
|
$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
|
# 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();
|
my $otmp = get_unused_workspace();
|
||||||
$i3->command("workspace $otmp")->recv;
|
cmd "workspace $otmp";
|
||||||
|
|
||||||
$i3->command("open")->recv;
|
$i3->command("open")->recv;
|
||||||
$i3->command("open")->recv;
|
$i3->command("open")->recv;
|
||||||
|
@ -125,9 +127,9 @@ $i3->command("open")->recv;
|
||||||
$i3->command("prev h")->recv;
|
$i3->command("prev h")->recv;
|
||||||
$i3->command("split v")->recv;
|
$i3->command("split v")->recv;
|
||||||
$i3->command("open")->recv;
|
$i3->command("open")->recv;
|
||||||
$i3->command("move after h")->recv;
|
$i3->command("move right")->recv;
|
||||||
$i3->command("prev h")->recv;
|
$i3->command("prev h")->recv;
|
||||||
$i3->command("move after h")->recv;
|
$i3->command("move right")->recv;
|
||||||
|
|
||||||
$content = get_ws_content($otmp);
|
$content = get_ws_content($otmp);
|
||||||
is(@{$content}, 1, 'only one nodes on this workspace');
|
is(@{$content}, 1, 'only one nodes on this workspace');
|
||||||
|
|
Loading…
Reference in New Issue