/* * split.c * Copyright © 2011 Martin Duquesnoy * 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; }