wmfs/src/split.c

374 lines
11 KiB
C

/*
* split.c
* Copyright © 2011 Martin Duquesnoy <xorg62@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of the nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "wmfs.h"
#define SPLIT_CHECK_ROW(g1, g2, p) \
(LDIR(p) \
? (g1.y >= g2.y && (g1.y + g1.height) <= (g2.y + g2.height)) \
: (g1.x >= g2.x && (g1.x + g1.width) <= (g2.x + g2.width))) \
#define SPLIT_MOVE_DIR(d) \
void \
uicb_split_move_##d(uicb_t cmd) \
{ \
CHECK(sel); \
split_move_dir(sel, d); \
}
/* uicb_split_move_dir() */
SPLIT_MOVE_DIR(Right);
SPLIT_MOVE_DIR(Left);
SPLIT_MOVE_DIR(Top);
SPLIT_MOVE_DIR(Bottom);
/** Arrange size of parent client of last closed client
*/
static void
_split_arrange_size(Geo g, Geo *cg, Position p)
{
if(LDIR(p))
cg->width += FRAMEW(g.width);
else
cg->height += FRAMEH(g.height);
if(p == Right)
cg->x -= FRAMEW(g.width);
if(p == Bottom)
cg->y -= FRAMEH(g.height);
return;
}
/** Set layout current clients to split/unsplit
*/
void
split_set_current(Client *nc, Client *ghost)
{
if(nc && (tags[nc->screen][nc->tag].flags & SplitFlag))
{
tags[nc->screen][nc->tag].layout.nc = nc;
tags[nc->screen][nc->tag].layout.flags |= IntegrationFlag;
}
if(ghost && (tags[ghost->screen][ghost->tag].flags & SplitFlag))
{
tags[ghost->screen][ghost->tag].layout.ghost = *ghost;
tags[ghost->screen][ghost->tag].layout.flags |= ArrangeFlag;
}
return;
}
/** Apply current operation about split
*/
void
split_apply_current(int screen, int tag)
{
/* Integrate in split mode */
if(tags[screen][tag].layout.flags & IntegrationFlag)
{
split_client_integrate(tags[screen][tag].layout.nc, sel, screen, tag);
tags[screen][tag].layout.flags &= ~IntegrationFlag;
}
/* Remove from split mode */
if(tags[screen][tag].layout.flags & ArrangeFlag)
{
split_arrange_closed(&tags[screen][tag].layout.ghost);
tags[screen][tag].layout.flags &= ~ArrangeFlag;
}
return;
}
/** Check if row direction is available to resize from it
*\param c Client pointer
*\param g Client pointer
*\param p Position
*\return True if available
*/
static bool
_split_check_row_dir(Client *c, Client *g, Position p)
{
int s, cs;
Geo cgeo;
Client *cc;
cs = (LDIR(p) ? g->frame_geo.height : g->frame_geo.width);
for(s = 0, cgeo = c->frame_geo, cc = tiled_client(c->screen, clients);
cc; cc = tiled_client(c->screen, cc->next))
if(CFACTOR_PARENTROW(cgeo, cc->frame_geo, RPOS(p))
&& SPLIT_CHECK_ROW(cc->frame_geo, g->frame_geo, p))
{
s += (LDIR(p) ? cc->frame_geo.height : cc->frame_geo.width);
if(s == cs)
return True;
if(s > cs)
return False;
}
return False;
}
/** Arrange clients after a client close
*\param ghost Ghost client
*/
void
split_arrange_closed(Client *ghost)
{
Position p;
bool b = False;
Geo cgeo;
Client *c, *cc;
int screen = ghost->screen;
int tag = (ghost->tag ? ghost->tag : seltag[screen]);
/* Use ghost client properties to fix holes in tile
* .--. ~ ~
* /xx \ ~ ~
* ~~\O _ (____ ~
* __.| .--'-==~ ~
* '---\ '. ~ , ~
* '. '-.___.-'/ ~
* '-.__ _.' ~
* ````` ~
*/
/* Search for single parent for easy resize
* Example case:
* ___________ ___________
* | | B | -> -> | | |
* | A |_____| -> Close -> | A | B |
* | | C | -> C -> | |v v v|
* |_____|_____| -> -> |_____|_____|
*/
for(p = Right; p < Bottom; ++p)
if((c = client_get_next_with_direction(ghost, p)))
if(CFACTOR_CHECK2(ghost->frame_geo, c->frame_geo, p))
{
_split_arrange_size(ghost->wrgeo, &c->wrgeo, p);
cfactor_clean(c);
client_moveresize(c, (c->pgeo = c->wrgeo), (tags[screen][tag].flags & ResizeHintFlag));
return;
}
/* Check row parents for full resize
* Example case:
* ___________ ___________
* | | B | -> -> | << B |
* | A |_____| -> Close -> |___________|
* | | C | -> A -> | << C |
* |_____|_____| -> -> |___________|
*/
for(p = Right; p < Bottom && !b; ++p)
if((c = client_get_next_with_direction(ghost, p)) && _split_check_row_dir(c, ghost, p))
{
for(cgeo = c->frame_geo, cc = tiled_client(c->screen, clients);
cc; cc = tiled_client(c->screen, cc->next))
if(CFACTOR_PARENTROW(cgeo, cc->frame_geo, RPOS(p))
&& SPLIT_CHECK_ROW(cc->frame_geo, ghost->frame_geo, p))
{
_split_arrange_size(ghost->wrgeo, &cc->wrgeo, p);
cfactor_clean(cc);
client_moveresize(cc, (cc->pgeo = cc->wrgeo), (tags[screen][tag].flags & ResizeHintFlag));
b = True;
}
}
return;
}
/** Split client hor or vert to insert another client in the new area
*\param c Client pointer
*\param p True = Vertical, False = Horizontal
*\return sgeo Geo of future integrated client
*/
Geo
split_client(Client *c, bool p)
{
Geo geo, sgeo;
if(!c || !(c->flags & TileFlag))
return c->wrgeo;
cfactor_clean(c);
c->flags &= ~(MaxFlag | LMaxFlag);
c->flags |= TileFlag;
/* Use geometry without resizehint applied on it */
geo = sgeo = c->wrgeo;
/* Vertical */
if(p)
{
geo.width >>= 1;
sgeo.x = FRAMEW(geo.x + geo.width);
sgeo.width = (sgeo.width >> 1) - (BORDH << 1);
/* Remainder */
sgeo.width += (c->wrgeo.x + c->wrgeo.width) - (sgeo.x + sgeo.width);
}
/* Horizontal */
else
{
geo.height = (geo.height >> 1) - TBARH;
sgeo.y = FRAMEH(geo.y + geo.height);
sgeo.height = (sgeo.height >> 1) - BORDH;
/* Remainder */
sgeo.height += (c->wrgeo.y + c->wrgeo.height) - (sgeo.y + sgeo.height);
}
client_moveresize(c, (c->pgeo = geo), (tags[c->screen][c->tag].flags & ResizeHintFlag));
return sgeo;
}
/** Apply new attributes to splitted client
*\param c Client pointer
*\param geo New geo
*/
void
split_client_fill(Client *c, Geo geo)
{
if(!c)
return;
c->flags &= ~(MaxFlag | LMaxFlag);
c->flags |= TileFlag;
cfactor_clean(c);
client_moveresize(c, (c->pgeo = geo), (tags[c->screen][c->tag].flags & ResizeHintFlag));
return;
}
/** Integrate client in tag
*\param c Client pointer (integrate)
*\param sc Splitted client pointer
*/
void
split_client_integrate(Client *c, Client *sc, int screen, int tag)
{
bool b = True;
Geo g;
if(!c || c->flags & FreeFlag || !(tags[screen][tag].flags & SplitFlag))
return;
/* Can't integrate in sc */
if(!sc || sc == c || !(sc->flags & TileFlag)
|| sc->screen != screen || sc->tag != tag)
{
/* Looking for first client on wanted tag */
for(b = False, sc = clients; sc; sc = sc->next)
if(sc != c && sc->screen == screen && sc->tag == tag
&& (sc->flags & TileFlag))
{
b = True;
break;
}
/* No client on wanted tag to integrate */
if(!b)
{
/* client_maximize check position of client
* to maximize it; so in case of transfert one client
* on a tag from another screen, we need it.
*/
c->geo.x = sgeo[screen].x;
c->geo.y = sgeo[screen].y;
client_maximize(c);
c->flags |= TileFlag;
return;
}
}
g = split_client(sc, (sc->frame_geo.height < sc->frame_geo.width));
split_client_fill(c, g);
return;
}
/** Move splitted client by re-arranging it in next by direction client
* Integrate c in next client by direction
* Example case, direction = left:
* ___________ ___________
* | | B | -> | A | |
* | A |_____| -> |_____| B |
* | |< C | -> | C |v v v|
* |_____|_____| -> |_____|_____|
*/
void
split_move_dir(Client *c, Position p)
{
Client *sc;
if(!c || !(tags[c->screen][c->tag].flags & SplitFlag))
return;
if((sc = client_get_next_with_direction(c, p)))
{
split_arrange_closed(c);
split_client_integrate(c, sc, sc->screen, sc->tag);
}
return;
}
/** Toggle split mode
*/
void
uicb_split_toggle(uicb_t cmd)
{
(void)cmd;
tags[selscreen][seltag[selscreen]].flags ^= SplitFlag;
layout_func(selscreen, seltag[selscreen]);
infobar_draw_layout(&infobar[selscreen]);
return;
}