Logo Search packages:      
Sourcecode: beryl-plugins-unsupported version File versions

tab.c

#include "group.h"

/**
 *
 * Beryl group plugin
 *
 * tab.c
 *
 * Copyright : (C) 2006 by Patrick Niklaus, Roi Cohen, Danny Baumann
 * Authors: Patrick Niklaus <patrick.niklaus@googlemail.com>
 *          Roi Cohen       <roico@beryl-project.org>
 *          Danny Baumann   <maniac@beryl-project.org>
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 **/

/*
 * groupGetCurrentMousePosition
 *
 */
Bool groupGetCurrentMousePosition(CompScreen *s, int *x, int *y)
{
      unsigned int rmask;
      int mouseX, mouseY, winX, winY;
      Window root;
      Window child;
      Bool result;
      
      result = XQueryPointer (s->display->display, s->root, &root, 
                  &child, &mouseX, &mouseY, &winX, &winY, &rmask);
      
      if (result) {
            (*x) = mouseX;
            (*y) = mouseY;
      }

      return result;
}

/*
 * groupGetWindowTitle - mostely copied from state.c
 *
 */
static char *groupGetWindowTitle(CompWindow *w)
{
      Atom type;
      int format;
      unsigned long nitems;
      unsigned long bytes_after;
      unsigned long *val;
      Display *d = w->screen->display->display;
      char *retval;
      int result;
      Atom utf8_string;

      utf8_string = XInternAtom(d, "UTF8_STRING", 0);

      type = None;
      val = NULL;
      result = XGetWindowProperty(d, w->id,
            XInternAtom(d, "_NET_WM_NAME", 0), 0, LONG_MAX, False, 
            utf8_string, &type, &format, &nitems, &bytes_after, 
            (unsigned char **)&val);

      if (result != Success)
            return NULL;

      if (type != utf8_string || format != 8 || nitems == 0)
      {
            if (val)
                  XFree(val);
            return NULL;
      }


      retval = (char *)malloc(sizeof(char) * (nitems + 1));
      strncpy(retval, (char *)val, nitems);
      retval[nitems] = '\0';

      XFree(val);

      return retval;
}

/*
 * groupGetClippingRegion
 *
 */
Region groupGetClippingRegion(CompWindow *w)
{
      Region clip = XCreateRegion();
      CompWindow *cw;
      for(cw = w->next; cw; cw = cw->next)
      {
            if (!cw->invisible && !(cw->state & (CompWindowStateHiddenMask | CompWindowStateOffscreenMask))) {
                  Region buf = XCreateRegion();
                  
                  XRectangle rect;
                  rect.x = WIN_REAL_X(cw);
                  rect.y = WIN_REAL_Y(cw);
                  rect.width = WIN_REAL_WIDTH(cw);
                  rect.height = WIN_REAL_HEIGHT(cw);
                  XUnionRectWithRegion(&rect, buf, buf);

                  XUnionRegion(clip, buf, clip);
                  XDestroyRegion(buf);
            }
      }

      return clip;
}

/*
 * groupTabBarTimeout
 *
 */
static Bool 
groupTabBarTimeout(void *data)
{
      GroupSelection *group = (GroupSelection *) data;

      groupTabSetVisibility(group, FALSE, PERMANENT);

      group->tabBar->timeoutHandle = 0;

      return FALSE;     //This will free the timer.
}

/*
 * groupCheckForVisibleTabBars
 *
 */
void
groupCheckForVisibleTabBars(CompScreen *s)
{
      GroupSelection *group;
      GROUP_SCREEN(s);

      gs->tabBarVisible = FALSE;

      for (group = gs->groups; group; group = group->next) {
            if (group->tabBar && (group->tabBar->state != PaintOff)) {
                  gs->tabBarVisible = TRUE;
                  break;
            }
      }
}

/*
 * groupTabSetVisibility
 *
 */
void groupTabSetVisibility(GroupSelection *group, Bool visible, unsigned int mask)
{
      // fixes bad crash...
      if (!group || !group->windows || !group->tabBar || !HAS_TOP_WIN(group))
            return;
      
      GroupTabBar *bar = group->tabBar;
      CompWindow *topTab = TOP_TAB(group);
      GROUP_SCREEN(group->screen);
      PaintState oldState;

      oldState = bar->state;

      /* hide tab bars for invisible top windows */
      if ((topTab->state & (CompWindowStateHiddenMask | 
                  CompWindowStateOffscreenMask)) || topTab->invisible)
      {
            bar->state = PaintOff;
            groupSwitchTopTabInput(group, TRUE);
      } 
      
      else if (visible && bar->state != PaintPermanentOn && 
                  (mask & PERMANENT))
      {
            bar->state = PaintPermanentOn;
            groupSwitchTopTabInput(group, FALSE);
      
      }
      
      else if (visible && 
            (bar->state == PaintOff || bar->state == PaintFadeOut))
      {
            bar->state = PaintFadeIn;     
            groupSwitchTopTabInput(group, FALSE);
      }
      
      else if (!visible && (bar->state != PaintPermanentOn || (mask & PERMANENT)) &&
            (bar->state == PaintOn || bar->state == PaintPermanentOn || bar->state == PaintFadeIn))
      {
            bar->state = PaintFadeOut;
            groupSwitchTopTabInput(group, TRUE);
      }

      if (bar->state != oldState && bar->state != PaintPermanentOn) // FIXME remove that when we have a new state for PaintPermanentFadeIn
            bar->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TIME].value.f * 1000) - 
                       bar->animationTime;

      groupCheckForVisibleTabBars(group->screen);
}

/*
 * groupGetDrawOffsetForSlot
 *
 */
void
groupGetDrawOffsetForSlot(GroupTabBarSlot *slot, int *hoffset, int *voffset)
{
      if (!slot || !slot->window)
            return;

      CompWindow *w = slot->window;

      GROUP_WINDOW(w);
      GROUP_SCREEN(w->screen);

      if (slot != gs->draggedSlot) {
            if (hoffset)
                  *hoffset = 0;
            if (voffset)
                  *voffset = 0;

            return;
      }

      int vx, vy;
      int oldX = w->serverX;
      int oldY = w->serverY;
      
      if (gw->group) {
            w->serverX = WIN_X(TOP_TAB(gw->group)) + WIN_WIDTH(TOP_TAB(gw->group)) / 2 - WIN_WIDTH(w) / 2;
            w->serverY = WIN_Y(TOP_TAB(gw->group)) + WIN_HEIGHT(TOP_TAB(gw->group)) / 2 - WIN_HEIGHT(w) / 2;
      }

      defaultViewportForWindow(w, &vx, &vy);

      if (hoffset)
            *hoffset = ((w->screen->x - vx) % w->screen->hsize) * w->screen->width;

      if (voffset)
            *voffset = ((w->screen->y - vy) % w->screen->vsize) * w->screen->height;

      w->serverX = oldX;
      w->serverY = oldY;
}

/*
 * groupHandleHoverDetection
 *
 */
void groupHandleHoverDetection(GroupSelection *group)
{
      GROUP_SCREEN(group->screen);
      GroupTabBar *bar = group->tabBar;

      if (!HAS_TOP_WIN(group))
            return;

      CompWindow *topTab = TOP_TAB(group);

      if (bar->state != PaintOff) { // Tab-bar is visible.
            int mouseX, mouseY;
            Bool mouseOnScreen;
            
            mouseOnScreen = groupGetCurrentMousePosition(group->screen, &mouseX, &mouseY);

            if (mouseOnScreen && !(bar->hoveredSlot && XPointInRegion(bar->hoveredSlot->region, mouseX, mouseY)))
            {
                  bar->hoveredSlot = NULL;
            
                  Region clip;
                  clip = groupGetClippingRegion(topTab);

                  GroupTabBarSlot *slot;
                  for (slot = bar->slots; slot; slot = slot->next)
                  {
                        Region reg = XCreateRegion();
                        XSubtractRegion(slot->region, clip, reg);

                        if (XPointInRegion(reg, mouseX, mouseY))
                        {
                              bar->hoveredSlot = slot;
                              break;
                        }

                        XDestroyRegion(reg);
                  }

                  XDestroyRegion(clip);
                  
                  if ((bar->textLayer->state == PaintFadeIn || bar->textLayer->state == PaintOn) &&
                      bar->hoveredSlot != bar->textSlot)
                  {
                        bar->textLayer->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000)
                                                                        - bar->textLayer->animationTime;
                        bar->textLayer->state = PaintFadeOut;
                  }
                  
                  else if (bar->textLayer->state == PaintFadeOut && bar->hoveredSlot == bar->textSlot && bar->hoveredSlot)
                  {
                        bar->textLayer->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000)
                                                                        - bar->textLayer->animationTime;
                        bar->textLayer->state = PaintFadeIn;
                  }
            }
      }
}

/*
 * groupHandleTabBarFade
 *
 */
void groupHandleTabBarFade(GroupSelection *group, int msSinceLastPaint)
{
      GroupTabBar *bar = group->tabBar;

      if ((bar->state == PaintFadeIn || bar->state == PaintFadeOut) &&
          bar->animationTime > 0)
      {
            bar->animationTime -= msSinceLastPaint;
            
            if (bar->animationTime < 0)
                  bar->animationTime = 0;

            if (bar->animationTime == 0)
            {
                  if (bar->state == PaintFadeIn) {
                        bar->state = PaintOn;
                        groupCheckForVisibleTabBars(group->screen);
                  }

                  else if (bar->state == PaintFadeOut) {
                        bar->state = PaintOff;
                        groupCheckForVisibleTabBars(group->screen);
            
                        if (bar->textLayer)     {
                              // Tab-bar is no longer painted, clean up text animation variables.
                              bar->textLayer->animationTime = 0;
                              bar->textLayer->state = PaintOff;
                              bar->textSlot = bar->hoveredSlot = NULL;
                                    
                              groupRenderWindowTitle(group);
                        }
                  }
            }
      }
}

/*
 * groupHanldeTextFade
 *
 */
void groupHandleTextFade(GroupSelection *group, int msSinceLastPaint)
{
      GROUP_SCREEN(group->screen);
      GroupTabBar *bar = group->tabBar;
      GroupCairoLayer *textLayer = bar->textLayer;          

      if (!textLayer)
            return;

      if ((textLayer->state == PaintFadeIn || textLayer->state == PaintFadeOut) &&
          textLayer->animationTime > 0)
      {
            textLayer->animationTime -= msSinceLastPaint;
            
            if (textLayer->animationTime < 0)
                  textLayer->animationTime = 0;
                  
            if (textLayer->animationTime == 0) {
                  if (textLayer->state == PaintFadeIn)
                        textLayer->state = PaintOn;
                  
                  else if (textLayer->state == PaintFadeOut)
                        textLayer->state = PaintOff;
            }
      }
            
      if (textLayer->state == PaintOff && bar->hoveredSlot)
      {
            // Start text animation for the new hovered slot.
            bar->textSlot = bar->hoveredSlot;
            textLayer->state = PaintFadeIn;
            textLayer->animationTime =  (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000);
                  
            groupRenderWindowTitle(group);
      }
            
      else if (textLayer->state == PaintOff && bar->textSlot)
      {
            // Clean Up.
            bar->textSlot = NULL;
            groupRenderWindowTitle(group);
      }
}

/*
 * groupHandleTabChange
 *
 */
static void
groupHandleTabChange(CompScreen *s, GroupSelection *group)
{
      GROUP_SCREEN(s);

      if (!group || !HAS_TOP_WIN(group) || !group->changeTab)
            return;

      if (screenGrabExist(s, "rotate", "plane", 0))
            return;

      CompWindow* topTab = TOP_TAB(group);

      if(group->tabbingState != PaintOff) 
      {
            // if the previous top-tab window is being removed from the group, move the new top-tab window onscreen.
            if(group->ungroupState == UngroupSingle && group->prevTopTab == NULL)
            {
                  gs->queued = TRUE;
                  moveWindowOnscreen(topTab);
                  moveWindow(topTab, group->oldTopTabCenterX - WIN_X(topTab) - WIN_WIDTH(topTab) / 2, 
                        group->oldTopTabCenterY - WIN_Y(topTab) - WIN_HEIGHT(topTab) / 2, 
                        TRUE, TRUE); 
                  syncWindowPosition(topTab);
                  gs->queued = FALSE;
                  
                  // recalc here is needed (for y value)!
                  groupRecalcTabBarPos(group, (group->tabBar->region->extents.x1 + group->tabBar->region->extents.x2) / 2,
                        WIN_REAL_X(topTab), 
                        WIN_REAL_X(topTab) + WIN_REAL_WIDTH(topTab));
                  
                  group->prevTopTab = group->topTab;
            }
            
            return;
      }

      gs->queued = TRUE;
      moveWindowOnscreen(topTab);
      moveWindow(topTab, group->oldTopTabCenterX - WIN_X(topTab) - WIN_WIDTH(topTab) / 2, 
            group->oldTopTabCenterY - WIN_Y(topTab) - WIN_HEIGHT(topTab) / 2, 
            TRUE, TRUE); 
      syncWindowPosition(topTab);
      gs->queued = FALSE;

      activateWindow (topTab);
                  
      if(group->prevTopTab) {
            //we use only the half time here - the second half will be PaintFadeOut
            group->changeAnimationTime = gs->opt[GROUP_SCREEN_OPTION_CHANGE_ANIMATION_TIME].value.f * 500;
            
            group->changeState = PaintFadeIn;
                        
            group->changeTab = FALSE;
      }
      
      else  //No window to do animation with.
      {
            group->prevTopTab = group->topTab;
            group->changeTab = FALSE;
      }
      
      return;
}

/*
 * groupHandleAnimation
 *
 */
static void
groupHandleAnimation(CompScreen *s, GroupSelection *group)
{
      GROUP_SCREEN(s);

      if(group->tabbingState != PaintOff || !HAS_TOP_WIN(group))
            return;

      if (screenGrabExist(s, "rotate", "plane", 0))
            return;

      if(group->changeState == PaintFadeIn && group->changeAnimationTime <= 0) {

            // recalc here is needed (for y value)!
            groupRecalcTabBarPos(group, (group->tabBar->region->extents.x1 + group->tabBar->region->extents.x2) / 2,
                  WIN_REAL_X(TOP_TAB(group)), 
                  WIN_REAL_X(TOP_TAB(group)) + WIN_REAL_WIDTH(TOP_TAB(group)));

            group->changeAnimationTime = gs->opt[GROUP_SCREEN_OPTION_CHANGE_ANIMATION_TIME].value.f * 500 + group->changeAnimationTime;
            
            group->changeState = PaintFadeOut;
      }
                        
      if (group->changeState == PaintFadeOut && group->changeAnimationTime <= 0)
      {                       
            if (group->prevTopTab)
                  moveWindowOffscreen(PREV_TOP_TAB(group));
                  
            group->prevTopTab = group->topTab;
                              
            group->changeState = PaintOff;
            group->changeAnimationTime = 0;
                              
            if (group->nextTopTab) {
                  groupChangeTab(group->nextTopTab, group->nextDirection);
                  group->nextTopTab = NULL;
                                    
                  groupHandleTabChange(s, group);
            }
            
            else if (gs->opt[GROUP_SCREEN_OPTION_VISIBILITY_TIME].value.f != 0.0f) {
                  groupTabSetVisibility (group, TRUE, PERMANENT | SHOW_BAR_INSTANTLY_MASK);
                  
                  if (group->tabBar->timeoutHandle)
                        compRemoveTimeout(group->tabBar->timeoutHandle);
            
                  group->tabBar->timeoutHandle = compAddTimeout (
                        gs->opt[GROUP_SCREEN_OPTION_VISIBILITY_TIME].value.f * 1000, 
                        groupTabBarTimeout, group);
            }
      }

      return;
}

/*
 * groupHandleTab
 *
 */
static void
groupHandleTab(CompScreen *s, GroupSelection *group)
{
      if (group->tabbingState == PaintOff || group->doTabbing ||
          !HAS_TOP_WIN(group) || !group->changeTab)
            return;
            
      GroupTabBarSlot *slot;
                  
      for(slot = group->tabBar->slots; slot; slot = slot->next) {
            if (!slot->window)
                  continue;

            CompWindow *w = slot->window;
            GROUP_WINDOW(w);

            if (slot == group->topTab || !(gw->animateState & FINISHED_ANIMATION) || gw->ungroup)
                  continue;

            moveWindowOffscreen(w);
      }

      group->changeTab = FALSE;
      group->prevTopTab = group->topTab;
      
      return;
}

/*
 * groupHandleTabbingAnimation
 *
 */
static void
groupHandleTabbingAnimation(CompScreen *s, GroupSelection *group)
{
      int i;

      if (group->tabbingState == PaintOff || group->doTabbing)
            return;
      
      // Not animated any more.
      group->tabbingState = PaintOff;
      groupSyncWindows(group);

      for(i = 0; i < group->nWins; i++) {
            CompWindow *w = group->windows[i];
            GROUP_WINDOW(w);

            gw->animateState = 0;
      }

      return;
}

/*
 * groupHandleUntab
 *
 */
static void
groupHandleUntab(CompScreen *s, GroupSelection *group)
{
      if (group->tabbingState == PaintOff || !group->doTabbing)
            return;

      if (group->topTab || !group->changeTab)
            return;

      groupDeleteTabBar(group);
                        
      group->changeAnimationTime = 0;
      group->changeState = PaintOff;
      group->nextTopTab = NULL;

      group->changeTab = FALSE;
      group->prevTopTab = group->topTab;
      
      return;
}

/*
 * groupHandleUngroup
 *
 */
static Bool
groupHandleUngroup(CompScreen *s, GroupSelection *group)
{
      int i;
      GROUP_SCREEN(s);

      if((group->ungroupState == UngroupSingle) && group->doTabbing && group->changeTab)
      {
            for(i = 0; i < group->nWins; i++) {
                  CompWindow *w = group->windows[i];
                  GROUP_WINDOW(w);
                  
                  if (gw->ungroup)
                  {
                        gs->queued = TRUE;
                        moveWindowOnscreen(w);
                        moveWindow(w, group->oldTopTabCenterX - WIN_X(w) - WIN_WIDTH(w) / 2, 
                              group->oldTopTabCenterY - WIN_Y(w) - WIN_HEIGHT(w) / 2, 
                              TRUE, TRUE);
                        syncWindowPosition(w);
                        gs->queued = FALSE;
                  }
            }
            
            group->changeTab = FALSE;
            
      }
      
      if ((group->ungroupState == UngroupSingle) && !group->doTabbing) {
            Bool morePending;

            do {
                  morePending = FALSE;

                  for(i = 0;i < group->nWins; i++) {
                        CompWindow *w = group->windows[i];
                        GROUP_WINDOW(w);
                  
                        if (gw->ungroup) {
                              groupDeleteGroupWindow(w, TRUE);
                              gw->ungroup = FALSE;
                              morePending = TRUE;
                        }
                  }
            } while (morePending);

            group->ungroupState = UngroupNone;
      }

      if (group->prev)
      {
            if ((group->prev->ungroupState == UngroupAll) && !group->prev->doTabbing)
                  groupDeleteGroup(group->prev);
      }
      if (!group->next) {
            if ((group->ungroupState == UngroupAll) && !group->doTabbing) {
                  groupDeleteGroup(group);
                  return FALSE;
            }
      }

      return TRUE;
}

/*
 * groupHandleChanges
 *
 */
void
groupHandleChanges(CompScreen* s)
{
      GROUP_SCREEN(s);

      GroupSelection *group;

      for(group = gs->groups; group; group = group ? group->next : NULL)
      {
            groupHandleUntab(s, group);
            groupHandleTab(s, group);
            groupHandleTabbingAnimation(s, group);
            groupHandleTabChange(s, group);
            groupHandleAnimation(s, group);

            if (!groupHandleUngroup(s, group))
                  group = NULL;
      }
}

/* adjust velocity for each animation step (adapted from the scale plugin) */
static int 
adjustTabVelocity(CompWindow * w)
{
      float dx, dy, adjust, amount;
      float x1, y1;

      GROUP_WINDOW(w);

      x1 = y1 = 0.0;

      if (!(gw->animateState & IS_ANIMATED))
            return 0;

      x1 = gw->destination.x;
      y1 = gw->destination.y;

      dx = x1 - (w->serverX + gw->tx);

      adjust = dx * 0.15f;
      amount = fabs(dx) * 1.5f;
      if (amount < 0.5f)
            amount = 0.5f;
      else if (amount > 5.0f)
            amount = 5.0f;

      gw->xVelocity = (amount * gw->xVelocity + adjust) / (amount + 1.0f);

      dy = y1 - (w->serverY + gw->ty);

      adjust = dy * 0.15f;
      amount = fabs(dy) * 1.5f;
      if (amount < 0.5f)
            amount = 0.5f;
      else if (amount > 5.0f)
            amount = 5.0f;

      gw->yVelocity = (amount * gw->yVelocity + adjust) / (amount + 1.0f);

      if (fabs(dx) < 0.1f && fabs(gw->xVelocity) < 0.2f &&
            fabs(dy) < 0.1f && fabs(gw->yVelocity) < 0.2f)
      {
            gw->xVelocity = gw->yVelocity = 0.0f;
            gw->tx = x1 - w->serverX;
            gw->ty = y1 - w->serverY;

            return 0;
      }
      return 1;
}

/*
 * groupDrawTabAnimation
 *
 */
void groupDrawTabAnimation(CompScreen * s, int msSinceLastPaint)
{
      GROUP_SCREEN(s);

      int i;
      
      GroupSelection *group;
      for(group = gs->groups; group; group = group->next)
      {
            if(group->tabbingState == PaintOff)
                  continue;

            int steps, dx, dy;
            float amount, chunk;

            amount = msSinceLastPaint * 0.05f * 
                gs->opt[GROUP_SCREEN_OPTION_TABBING_SPEED].value.f;
            steps = amount / (0.5f * 
                  gs->opt[GROUP_SCREEN_OPTION_TABBING_TIMESTEP].value.f);
            if (!steps)
                  steps = 1;
            chunk = amount / (float)steps;

            while (steps--) {
                  group->doTabbing = FALSE;

                  for(i = 0; i < group->nWins; i++) {
                        CompWindow *cw = group->windows[i];

                        if(!cw)
                              continue;

                        GROUP_WINDOW(cw);

                        if (!(gw->animateState & IS_ANIMATED))
                              continue;

                        if (!adjustTabVelocity(cw))
                        {
                              gw->animateState |= FINISHED_ANIMATION;
                              gw->animateState &= ~(IS_ANIMATED);
                        }

                        gw->tx += gw->xVelocity * chunk;
                        gw->ty += gw->yVelocity * chunk;

                        dx = (cw->serverX + gw->tx) - cw->attrib.x;
                        dy = (cw->serverY + gw->ty) - cw->attrib.y;

                        group->doTabbing |= (gw->animateState & IS_ANIMATED);

                        gs->queued = TRUE;
                        moveWindow(cw, dx, dy, FALSE, FALSE);
                        gs->queued = FALSE;
                  }
                  if (!group->doTabbing)
                        break;
            }
      }
}

/*
 * groupUpdateTabBars
 *
 */
Bool groupUpdateTabBars(void *display)
{
      CompDisplay *d = (CompDisplay*) display;

      GroupTabBar *bar;
      CompWindow *topTab;
      GroupSelection *group;
      
      Bool titleBarMouseOver;
      Bool tabBarMouseOver;
      
      int mouseX, mouseY;

      CompScreen *s;

      for (s = d->screens; s; s = s->next) {
            if (groupGetCurrentMousePosition(s, &mouseX, &mouseY))
                  break;
      }

      if (!s)
            return TRUE;

      GROUP_SCREEN(s);
      
      for(group = gs->groups; group; group = group->next)
      {     
            if(!group->tabBar || !HAS_TOP_WIN(group))
                  continue;
            
            topTab = TOP_TAB(group);

            if (topTab->state & (CompWindowStateHiddenMask | CompWindowStateOffscreenMask))
                  continue;
            
            if (topTab->invisible)
                  continue;

            bar = group->tabBar;

            // create clipping region
            Region clip = groupGetClippingRegion(topTab); 
            
            // title bar
            Region reg = XCreateRegion();
            XRectangle rect;
            rect.x = WIN_X(topTab) - topTab->input.left;
            rect.y = WIN_Y(topTab) - topTab->input.top;
            rect.width = WIN_WIDTH(topTab) + topTab->input.right;
            rect.height = WIN_Y(topTab) - rect.y;
            XUnionRectWithRegion(&rect, reg, reg);

            // clip title bar with stack
            XSubtractRegion(reg, clip, reg);

            titleBarMouseOver = XPointInRegion(reg, mouseX, mouseY);
            XDestroyRegion(reg);

            // clip tab bar with stack
            reg = XCreateRegion();
            XSubtractRegion(bar->region, clip, reg);
            
            if (bar->state != PaintOff && bar->state != PaintFadeOut)
                  tabBarMouseOver = XPointInRegion(reg, mouseX, mouseY);
            else
                  tabBarMouseOver = FALSE;

            if ((bar->state == PaintOff || bar->state == PaintFadeOut) && titleBarMouseOver) {
                  groupRecalcTabBarPos(group, mouseX, WIN_REAL_X(topTab), 
                        WIN_REAL_X(topTab) + WIN_REAL_WIDTH(topTab));
                  addWindowDamage(topTab);
            }

            XDestroyRegion(reg);
            XDestroyRegion(clip);

            groupTabSetVisibility (group, titleBarMouseOver | tabBarMouseOver, 0);
      }
      
      return TRUE;
}

/*
 * groupGetConstrainRegion
 *
 */
static Region groupGetConstrainRegion(CompScreen *s)
{
      CompWindow *w;
      Region     region;
      REGION     r;
      int        i;
             
      region = XCreateRegion ();
      if (!region)
            return NULL;

      for (i = 0;i < s->nOutputDev; i++)
            XUnionRegion (&s->outputDev[i].region, region, region);
      
      r.rects    = &r.extents;
      r.numRects = r.size = 1;
      
      for (w = s->windows; w; w = w->next) {
            if (!w->mapNum)
                   continue;

            if (w->struts) {
                  r.extents.x1 = w->struts->top.x;
                  r.extents.y1 = w->struts->top.y;
                        r.extents.x2 = r.extents.x1 + w->struts->top.width;
                  r.extents.y2 = r.extents.y1 + w->struts->top.height;
                  
                  XSubtractRegion (region, &r, region);

                  r.extents.x1 = w->struts->bottom.x;
                  r.extents.y1 = w->struts->bottom.y;
                  r.extents.x2 = r.extents.x1 + w->struts->bottom.width;
                  r.extents.y2 = r.extents.y1 + w->struts->bottom.height;
                   
                  XSubtractRegion (region, &r, region);

                  r.extents.x1 = w->struts->left.x;
                  r.extents.y1 = w->struts->left.y;
                  r.extents.x2 = r.extents.x1 + w->struts->left.width;
                  r.extents.y2 = r.extents.y1 + w->struts->left.height;
                   
                  XSubtractRegion (region, &r, region);

                  r.extents.x1 = w->struts->right.x;
                  r.extents.y1 = w->struts->right.y;
                  r.extents.x2 = r.extents.x1 + w->struts->right.width;
                  r.extents.y2 = r.extents.y1 + w->struts->right.height;
                   
                  XSubtractRegion (region, &r, region);
            }
      }

      return region;
}

/*
 * groupConstrainMovement
 *
 */
static Bool groupConstrainMovement(CompWindow *w, Region constrainRegion, int dx, int dy, 
      int *new_dx, int *new_dy)
{
      GROUP_WINDOW(w);
      int status;
      int origDx = dx, origDy = dy;
      int x, y, width, height;

      if (!gw->group)
            return FALSE;

      if (!dx && !dy)
            return FALSE;

      x = gw->orgPos.x - w->input.left + dx;
      y = gw->orgPos.y - w->input.top + dy;
      width = WIN_REAL_WIDTH(w);
      height = WIN_REAL_HEIGHT(w);

      status = XRectInRegion(constrainRegion, x, y, width, height);

      int xStatus = status;
      while (dx && (xStatus != RectangleIn)) {
            xStatus = XRectInRegion(constrainRegion, x, y - dy,
                              width, height);
                  
            if (xStatus != RectangleIn)
                  dx += (dx < 0) ? 1 : -1;

            x = gw->orgPos.x - w->input.left + dx;
      }

      while (dy && (status != RectangleIn)) {
            status = XRectInRegion(constrainRegion, x, y, 
                        width, height);

            if (status != RectangleIn)
                  dy += (dy < 0) ? 1 : -1;

            y = gw->orgPos.y - w->input.top + dy;
      }

      if (new_dx)
            *new_dx = dx;

      if (new_dy)
            *new_dy = dy;

      if ((dx != origDx) || (dy != origDy))
            return TRUE;
      else
            return FALSE;
}

/*
 * groupApplyConstrainingToWindows
 *
 */
static void groupApplyConstrainingToWindows(GroupSelection *group, 
      Region constrainRegion, Window constrainedWindow, int dx, int dy)
{
      int i;
      CompWindow *w;

      if (!dx && !dy)
            return;

      for (i = 0; i < group->nWins; i++) {
            w = group->windows[i];
            GROUP_WINDOW(w);

            /* ignore certain windows: we don't want to apply
               the constraining results on the constrained window
               itself, not do we want to change the target position
               of unamimated windows and of windows which
               already are constrained */
            if (w->id == constrainedWindow)
                  continue;

            if(!(gw->animateState & IS_ANIMATED))
                  continue;

            if (gw->animateState & DONT_CONSTRAIN)
                  continue;

            if (!(gw->animateState & CONSTRAINED_X)) {
                  gw->animateState |= IS_ANIMATED;

                  /* applying the constraining result of another window
                     might move the window offscreen, too, so check
                     if this is not the case */
                  if (groupConstrainMovement(w, constrainRegion, dx, 0, &dx, NULL))
                        gw->animateState |= CONSTRAINED_X;

                  gw->destination.x += dx;
                  gw->orgPos.x += dx;
            }

            if (!(gw->animateState & CONSTRAINED_Y)) {
                  gw->animateState |= IS_ANIMATED;

                  /* analog to X case */
                  if (groupConstrainMovement(w, constrainRegion, 0, dy, NULL, &dy))
                        gw->animateState |= CONSTRAINED_Y;

                  gw->destination.y += dy;
                  gw->orgPos.y += dy;
            }
      }
}

/*
 * groupStartTabbingAnimation
 *
 */
void groupStartTabbingAnimation(GroupSelection *group, Bool tab)
{
      if (!group || (group->tabbingState != PaintOff))
            return;

      CompScreen *s = group->windows[0]->screen;
      int i;
      int dx, dy;
      int constrainStatus;

      group->doTabbing = TRUE;
      group->changeTab = TRUE;

      group->tabbingState = (tab) ? PaintFadeIn : PaintFadeOut;

      if (!tab) {
            /* we need to set up the X/Y constraining on untabbing */
            Region constrainRegion = groupGetConstrainRegion(s);
            Bool constrainedWindows = TRUE;

            if (!constrainRegion)
                  return;

            /* reset all flags */
            for (i = 0; i < group->nWins; i++) {
                  GROUP_WINDOW(group->windows[i]);
                  gw->animateState &= ~(CONSTRAINED_X | CONSTRAINED_Y | DONT_CONSTRAIN);
            }

            /* as we apply the constraining in a flat loop,
               we may need to run multiple times through this
               loop until all constraining dependencies are met */
            while (constrainedWindows) {
                  constrainedWindows = FALSE;
                  /* loop through all windows and try to constrain their
                     animation path (going from gw->orgPos to 
                     gw->destination) to the active screen area */
                  for (i = 0; i < group->nWins; i++) {
                        CompWindow *w = group->windows[i];
                        GROUP_WINDOW(w);

                        /* ignore windows which aren't animated and/or
                           already are at the edge of the screen area */
                        if (!(gw->animateState & IS_ANIMATED))
                              continue;

                        if (gw->animateState & DONT_CONSTRAIN)
                              continue;

                        /* is the original position inside the screen area? */
                        constrainStatus = XRectInRegion(constrainRegion,
                                    gw->orgPos.x  - w->input.left, 
                                    gw->orgPos.y - w->input.top,
                                    WIN_REAL_WIDTH(w), WIN_REAL_HEIGHT(w));

                        /* constrain the movement */
                        if (groupConstrainMovement(w, constrainRegion,
                                  gw->destination.x - gw->orgPos.x,
                                  gw->destination.y - gw->orgPos.y, &dx, &dy)) {
                              /* handle the case where the window is outside the screen
                                 area on its whole animation path */
                              if (constrainStatus != RectangleIn && !dx && !dy) {
                                    gw->animateState |= DONT_CONSTRAIN;
                                    gw->animateState |= CONSTRAINED_X | CONSTRAINED_Y;

                                    /* use the original position as last resort */
                                    gw->destination.x = gw->mainTabOffset.x;
                                    gw->destination.y = gw->mainTabOffset.y;
                              } else {
                                    /* if we found a valid target position, apply
                                       the change also to other windows to retain
                                       the distance between the windows */
                                    groupApplyConstrainingToWindows(group, 
                                          constrainRegion, w->id, 
                                          dx - gw->destination.x + gw->orgPos.x, 
                                          dy - gw->destination.y + gw->orgPos.y);

                                    /* if we hit constraints, adjust the mask and the 
                                       target position accordingly */
                                    if (dx != (gw->destination.x - gw->orgPos.x)) {
                                          gw->animateState |= CONSTRAINED_X;
                                          gw->destination.x = gw->orgPos.x + dx;
                                    }
                                    
                                    if (dy != (gw->destination.y - gw->orgPos.y)) {
                                          gw->animateState |= CONSTRAINED_Y;
                                          gw->destination.y = gw->orgPos.y + dy;
                                    }

                                    constrainedWindows = TRUE;
                              }
                        }
                  }
            }
            XDestroyRegion(constrainRegion);
      }
}

/*
 * groupTabGroup
 *
 */
void groupTabGroup(CompWindow *main)
{
      GROUP_WINDOW(main);
      GROUP_SCREEN(main->screen);
      GroupSelection *group = gw->group;

      if(!group || group->tabBar)
            return;

      groupInitTabBar(group, main);
      groupCreateInputPreventionWindow(group);
      
      group->tabbingState = PaintOff;
      groupChangeTab(gw->slot, RotateUncertain);      //Slot is initialized after groupInitTabBar(group);
      groupRecalcTabBarPos(gw->group, WIN_X(main) + WIN_WIDTH(main)/2,   
                  WIN_X(main), WIN_X(main) + WIN_WIDTH(main)); 
      
      int width, height;
      width = group->tabBar->region->extents.x2 - group->tabBar->region->extents.x1;
      height = group->tabBar->region->extents.y2 - group->tabBar->region->extents.y1;
      
      group->tabBar->textLayer = groupCreateCairoLayer(main->screen, width, height);
      group->tabBar->textLayer->state = PaintOff;
      group->tabBar->textLayer->animationTime = 0;
      groupRenderWindowTitle(group);
      group->tabBar->textLayer->animationTime = gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000;
      group->tabBar->textLayer->state = PaintFadeIn;
      
      group->tabBar->bgLayer = groupCreateCairoLayer(main->screen, width, height);
      group->tabBar->bgLayer->state = PaintOn;
      group->tabBar->bgLayer->animationTime = 0;
      groupRenderTabBarBackground(group);

      width = group->topTab->region->extents.x2 - group->topTab->region->extents.x1;
      height = group->topTab->region->extents.y2 - group->topTab->region->extents.y1;

      group->tabBar->selectionLayer = groupCreateCairoLayer(main->screen, width, height);
      group->tabBar->selectionLayer->state = PaintOn;
      group->tabBar->selectionLayer->animationTime = 0;
      groupRenderTopTabHighlight(group);

      if(!HAS_TOP_WIN(group))
            return;

      GroupTabBarSlot *slot;
                              
      for(slot = group->tabBar->slots; slot; slot = slot->next) {       
            CompWindow *cw = slot->window;

            GROUP_WINDOW(cw);
            
            int x = WIN_X(cw), y = WIN_Y(cw);
            
            if(gw->animateState & IS_ANIMATED)
            {
                  x = gw->destination.x;
                  y = gw->destination.y;
            }
            
            // center the window to the main window
            gw->destination.x = WIN_X(main) + (WIN_WIDTH(main) / 2) - (WIN_WIDTH(cw) / 2);
            gw->destination.y = WIN_Y(main) + (WIN_HEIGHT(main) / 2) - (WIN_HEIGHT(cw) / 2);

            gw->mainTabOffset.x = x - gw->destination.x;    //Distance from destination.
            gw->mainTabOffset.y = y - gw->destination.y;

            gw->orgPos.x = WIN_X(cw);
            gw->orgPos.y = WIN_Y(cw);

            gw->tx = gw->ty = gw->xVelocity = gw->yVelocity = 0.0f;

            gw->animateState |= IS_ANIMATED;
      }

      groupStartTabbingAnimation(group, TRUE);
}

/*
 * groupUntabGroup
 *
 */
void
groupUntabGroup(GroupSelection *group)
{
      if(!HAS_TOP_WIN(group))
            return;

      GROUP_WINDOW(TOP_TAB(group));
      GROUP_SCREEN(TOP_TAB(group)->screen);     
      
      int mainOrgPosX = gw->mainTabOffset.x;
      int mainOrgPosY = gw->mainTabOffset.y;
      int oldX, oldY;
      
      CompWindow* prevTopTab;
      
      if(group->prevTopTab)
            prevTopTab = PREV_TOP_TAB(group);
      else
            prevTopTab = TOP_TAB(group);  //If prevTopTab isn't set, we have no choice but using topTab.
                                    //It happens when there is still animation, which means the tab wasn't changed anyway.
      
      group->oldTopTabCenterX = WIN_X(prevTopTab) + WIN_WIDTH(prevTopTab)/2;
      group->oldTopTabCenterY = WIN_Y(prevTopTab) + WIN_HEIGHT(prevTopTab)/2;
      
      group->topTab = NULL;

      GroupTabBarSlot *slot;
            
      for(slot = group->tabBar->slots; slot; slot = slot->next) {

            CompWindow *cw = slot->window;

            GROUP_WINDOW(cw);

            gs->queued = TRUE;
            moveWindowOnscreen(cw);
            moveWindow(cw, group->oldTopTabCenterX - WIN_X(cw) - WIN_WIDTH(cw) / 2, 
                  group->oldTopTabCenterY - WIN_Y(cw) - WIN_HEIGHT(cw) / 2, 
                  TRUE, TRUE);
            syncWindowPosition(cw);
            gs->queued = FALSE;

            /* save the old original position - we might need it if constraining fails */
            oldX = gw->orgPos.x;
            oldY = gw->orgPos.y;

            gw->orgPos.x = group->oldTopTabCenterX - WIN_WIDTH(cw) / 2;
            gw->orgPos.y = group->oldTopTabCenterY - WIN_HEIGHT(cw) / 2;

            gw->destination.x = WIN_X(prevTopTab) + WIN_WIDTH(prevTopTab)/2 - WIN_WIDTH(cw)/2 + gw->mainTabOffset.x - mainOrgPosX;
            gw->destination.y = WIN_Y(prevTopTab) + WIN_HEIGHT(prevTopTab)/2 - WIN_HEIGHT(cw)/2 + gw->mainTabOffset.y - mainOrgPosY;

            gw->mainTabOffset.x = oldX;
            gw->mainTabOffset.y = oldY;

            gw->animateState |= IS_ANIMATED;
            gw->tx = gw->ty = gw->xVelocity = gw->yVelocity = 0.0f;
      }

      group->tabbingState = PaintOff;
      groupStartTabbingAnimation(group, FALSE);

      damageScreen(group->screen);
}

/*
 * groupChangeTab
 *
 */
Bool
groupChangeTab(GroupTabBarSlot* topTab, ChangeTabAnimationDirection direction)
{
      if(!topTab)
            return TRUE;

      CompWindow* w = topTab->window;
      GROUP_WINDOW(w);

      if(!gw->group || gw->group->topTab == topTab || gw->group->tabbingState != PaintOff)
            return TRUE;

      if (gw->group->prevTopTab && gw->group->changeState == PaintOff) {
            gw->group->oldTopTabCenterX = WIN_X(PREV_TOP_TAB(gw->group)) + WIN_WIDTH(PREV_TOP_TAB(gw->group))/2;
            gw->group->oldTopTabCenterY = WIN_Y(PREV_TOP_TAB(gw->group)) + WIN_HEIGHT(PREV_TOP_TAB(gw->group))/2;
      }
      
      if(gw->group->changeState != PaintOff)
            gw->group->nextDirection = direction;
      else if (direction == RotateLeft) 
            gw->group->changeAnimationDirection = 1;
      else if (direction == RotateRight)
            gw->group->changeAnimationDirection = -1;
      else {
            int distanceOld = 0, distanceNew = 0;
            GroupTabBarSlot *slot;

            if (gw->group->topTab)
                  for (slot = gw->group->tabBar->slots; slot && (slot != gw->group->topTab); 
                        slot = slot->next, distanceOld++);

            for (slot = gw->group->tabBar->slots; slot && (slot != topTab); 
                  slot = slot->next, distanceNew++);

            if (distanceNew < distanceOld)
                  gw->group->changeAnimationDirection = 1;   //left
            else 
                  gw->group->changeAnimationDirection = -1;    //right

            //check if the opposite direction is shorter
            if (abs(distanceNew - distanceOld) > (gw->group->tabBar->nSlots / 2))
                  gw->group->changeAnimationDirection *= -1;
      }

      if(gw->group->changeState != PaintOff)
            gw->group->nextTopTab = topTab;
      
      else
      {
            gw->group->topTab = topTab;
            gw->group->changeTab = (gw->group->prevTopTab != topTab);
            
            groupRenderWindowTitle(gw->group);
            groupRenderTopTabHighlight(gw->group);
            addWindowDamage(w);
      }

      return TRUE;
}

/*
 * groupRebuildCairoLayer
 *
 */
GroupCairoLayer* groupRebuildCairoLayer(CompScreen *s, GroupCairoLayer *layer, int width, int height)
{
      int timeBuf = layer->animationTime;
      PaintState stateBuf = layer->state;

      groupDestroyCairoLayer(s, layer);
      layer = groupCreateCairoLayer(s, width, height);

      layer->animationTime = timeBuf;
      layer->state = stateBuf;

      return layer;
}

/*
 * groupClearCairoLayer
 *
 */
void groupClearCairoLayer(GroupCairoLayer *layer)
{
      cairo_t *cr = layer->cairo;
      cairo_save(cr);
      cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
      cairo_paint(cr);
      cairo_restore(cr);
}

/*
 * groupDestroyCairoLayer
 *
 */
void groupDestroyCairoLayer(CompScreen *s, GroupCairoLayer *layer)
{
      if (layer->cairo)
            cairo_destroy(layer->cairo);

      if (layer->surface)
            cairo_surface_destroy(layer->surface);

      if (layer->texBuf)
            free(layer->texBuf);

      if (&layer->texture)
            finiTexture(s, &layer->texture);

      if (layer->pixmap)
            XFreePixmap(s->display->display, layer->pixmap);

      free(layer);
}

/*
 * groupCreateCairoLayer
 *
 */
GroupCairoLayer* groupCreateCairoLayer(CompScreen *s, int width, int height)
{
      GroupCairoLayer* layer = (GroupCairoLayer*) malloc(sizeof(GroupCairoLayer));

      layer->surface    = NULL;
      layer->cairo      = NULL;
      layer->texBuf   = NULL;
      layer->animationTime = 0;
      layer->state = PaintOff;
      layer->pixmap = None;

      initTexture(s, &layer->texture);
      layer->texBuf = calloc(4 * width * height, sizeof (unsigned char));
      layer->surface = cairo_image_surface_create_for_data(layer->texBuf, CAIRO_FORMAT_ARGB32,
                                                                                    width, height, 4 * width);

      if (cairo_surface_status(layer->surface) != CAIRO_STATUS_SUCCESS) {
            free(layer->texBuf);
            free(layer);
        return NULL;
      }

      layer->cairo = cairo_create(layer->surface);
      if (cairo_status(layer->cairo) != CAIRO_STATUS_SUCCESS) {
            free(layer->texBuf);
            free(layer);
        return NULL;
      }

      groupClearCairoLayer(layer);

      return layer;
}

/*
 * groupRecalcSlotPos
 *
 */
static void 
groupRecalcSlotPos(GroupTabBarSlot *slot, int slotPos)
{
      GROUP_WINDOW (slot->window);
      GROUP_SCREEN (slot->window->screen);
      GroupSelection *group = gw->group;
      XRectangle box;

      if (!HAS_TOP_WIN(group) || !group->tabBar)
            return;

      int border_width = gs->opt[GROUP_SCREEN_OPTION_BORDER_WIDTH].value.i;
      int thumb_size = gs->opt[GROUP_SCREEN_OPTION_THUMB_SIZE].value.i;

      EMPTY_REGION(slot->region);

      box.x = border_width + ((thumb_size + border_width) * slotPos);
      box.y = border_width;

      box.width = thumb_size;
      box.height = thumb_size;

      XUnionRectWithRegion(&box, slot->region, slot->region);

      // in case the name changed we need a new one here
      if(slot->name)
            free(slot->name);
      slot->name = groupGetWindowTitle(slot->window);
}

/*
 * groupRecalcTabBarPos
 *
 */
void groupRecalcTabBarPos(GroupSelection *group, int middleX, int minX1, int maxX2)
{
      if (!HAS_TOP_WIN(group) || !group->tabBar)
            return;

      GroupTabBarSlot *slot;
      GroupTabBar *bar = group->tabBar;
      CompWindow *topTab = TOP_TAB(group);
      Bool isDraggedSlotGroup = FALSE;
      GROUP_SCREEN(group->screen);

      /* first damage the old region to make sure it is updated */
      damageScreenRegion (group->screen, bar->region);

      int border_width = gs->opt[GROUP_SCREEN_OPTION_BORDER_WIDTH].value.i;
      int bar_width;
      int currentSlot;
      XRectangle box;

      // calculate the space which the tabs need
      int tabs_width = 0;
      int tabs_height = 0;
      for(slot = bar->slots; slot; slot = slot->next)
      {
            if (slot == gs->draggedSlot && gs->dragged) {
                  isDraggedSlotGroup = TRUE;
                  continue;
            }
      
            tabs_width += (slot->region->extents.x2 - slot->region->extents.x1);
            if ((slot->region->extents.y2 - slot->region->extents.y1) > tabs_height)
                  tabs_height = slot->region->extents.y2 - slot->region->extents.y1;
      }

      // just a little work-a-round for first call
      int thumb_size = gs->opt[GROUP_SCREEN_OPTION_THUMB_SIZE].value.i;
      if (bar->nSlots && tabs_width <= 0) { // first call
            tabs_width = thumb_size * bar->nSlots;
            if (bar->nSlots && tabs_height < thumb_size) // we need to do the standard height too
                  tabs_height = thumb_size;
      }

      bar_width = border_width * (bar->nSlots + 1) + tabs_width;

      if (isDraggedSlotGroup)
            bar_width -= border_width;  //1 tab is missing, so we have 1 less border

      if (maxX2 - minX1 < bar_width) 
            box.x = (maxX2 + minX1)/2 - bar_width / 2;      
      else if (middleX - bar_width/2 < minX1)
            box.x = minX1;    
      else if (middleX + bar_width/2 > maxX2)
            box.x = maxX2 - bar_width;    
      else
            box.x = middleX - bar_width / 2;

      box.y = WIN_Y(topTab);
      box.width = bar_width;
      box.height = border_width * 2 + tabs_height;

      EMPTY_REGION(bar->region);
      XUnionRectWithRegion(&box, bar->region, bar->region);
      
      // recalc every slot region
      currentSlot = 0;
      for(slot = bar->slots; slot; slot = slot->next)
      {
            if(slot == gs->draggedSlot && gs->dragged)
                  continue;
      
            groupRecalcSlotPos (slot, currentSlot);
            XOffsetRegion (slot->region, 
                  bar->region->extents.x1,
                  bar->region->extents.y1);
            slot->springX = (slot->region->extents.x1 + slot->region->extents.x2) / 2;
            slot->speed = 0;
            slot->msSinceLastMove = 0;
            
            currentSlot++;
      }
      
      bar->leftSpringX = box.x;
      bar->rightSpringX = box.x + box.width;
      
      bar->rightSpeed = 0;
      bar->leftSpeed = 0;
      
      bar->rightMsSinceLastMove = 0;
      bar->leftMsSinceLastMove = 0;
      
      groupUpdateInputPreventionWindow(group);
      groupRenderTabBarBackground(group);
}

/*
 * groupInsertTabBarSlotBefore
 *
 */
void groupInsertTabBarSlotBefore(GroupTabBar *bar, GroupTabBarSlot *slot, GroupTabBarSlot *nextSlot)
{
      GroupTabBarSlot *prev = nextSlot->prev;

      if(prev) {
            slot->prev = prev;
            prev->next = slot;
      } else {
            bar->slots = slot;
            slot->prev = NULL;
      }

      slot->next = nextSlot;
      nextSlot->prev = slot;
      bar->nSlots++;

      CompWindow *w = slot->window;
      GROUP_WINDOW(w);
      //Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
      //because the tab-bar got wider now, so it will put it in the average between them,
      //which is (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway.
      groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
                       bar->region->extents.x1, bar->region->extents.x2);
}

/*
 * groupInsertTabBarSlotAfter
 *
 */
void groupInsertTabBarSlotAfter(GroupTabBar *bar, GroupTabBarSlot *slot, GroupTabBarSlot *prevSlot)
{
      GroupTabBarSlot *next = prevSlot->next;

      if (next) {
            slot->next = next;
            next->prev = slot;
      } else {
            bar->revSlots = slot;
            slot->next = NULL;
      }

      slot->prev = prevSlot;
      prevSlot->next = slot;
      bar->nSlots++;

      CompWindow *w = slot->window;
      GROUP_WINDOW(w);
      //Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
      //because the tab-bar got wider now, so it will put it in the average between them,
      //which is (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway.
      groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
                       bar->region->extents.x1, bar->region->extents.x2);
}

/*
 * groupInsertTabBarSlot
 *
 */
void groupInsertTabBarSlot(GroupTabBar *bar, GroupTabBarSlot *slot)
{

      if (bar->slots != NULL) {
                  bar->revSlots->next = slot;
                  slot->prev = bar->revSlots;
                  slot->next = NULL;
      } else {
                  slot->prev = NULL;
                  slot->next = NULL;
                  bar->slots = slot;
      }

      bar->revSlots = slot;
      bar->nSlots++;

      CompWindow *w = slot->window;
      GROUP_WINDOW(w);
      //Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
      //because the tab-bar got wider now, so it will put it in the average between them,
      //which is (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway.
      groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
                       bar->region->extents.x1, bar->region->extents.x2);
}

/*
 * groupUnhookTabBarSlot
 *
 */
void groupUnhookTabBarSlot(GroupTabBar *bar, GroupTabBarSlot *slot, Bool temporary)
{
      GroupTabBarSlot *prev = slot->prev;
      GroupTabBarSlot *next = slot->next;
      
      if(prev)
            prev->next = next;
      else
            bar->slots = next;
      
      if(next)
            next->prev = prev;
      else
            bar->revSlots = prev;

      slot->prev = NULL;
      slot->next = NULL;

      bar->nSlots--;

      CompWindow *w = slot->window;
      GROUP_WINDOW(w);
      GROUP_SCREEN(w->screen);

      if (IS_TOP_TAB(w, gw->group) && !temporary)
      {
            if(next)
                  groupChangeTab(next, RotateRight);
            else if(prev)
                  groupChangeTab(prev, RotateLeft);
            else if(gw->group->nWins == 1)
                  gw->group->topTab = NULL;

            if(gs->opt[GROUP_SCREEN_OPTION_UNTAB_ON_CLOSE].value.b)
                  groupUntabGroup(gw->group);
      }

      if(IS_PREV_TOP_TAB(w, gw->group) && !temporary)
            gw->group->prevTopTab = NULL;

      if (slot == bar->hoveredSlot)
            bar->hoveredSlot = NULL;
      
      if (slot == bar->textSlot)
      {
            bar->textSlot = NULL;
            
            if (bar->textLayer->state == PaintFadeIn || bar->textLayer->state == PaintOn)
            {
                  bar->textLayer->animationTime = (gs->opt[GROUP_SCREEN_OPTION_FADE_TEXT_TIME].value.f * 1000)
                                                                              - bar->textLayer->animationTime;
                  bar->textLayer->state = PaintFadeOut;
            }
      }

      //Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
      //because the tab-bar got thiner now, so (bar->region->extents.x1 + bar->region->extents.x2) / 2
      //Won't cause the new x1 / x2 to be outside the original region.
      groupRecalcTabBarPos(gw->group, (bar->region->extents.x1 + bar->region->extents.x2) / 2,
                       bar->region->extents.x1, bar->region->extents.x2);
}

/*
 * groupDeleteTabBarSlot
 *
 */
void groupDeleteTabBarSlot(GroupTabBar *bar, GroupTabBarSlot *slot)
{
      groupUnhookTabBarSlot(bar, slot, FALSE);

      if (slot->region)
            XDestroyRegion(slot->region);
      if (slot->name)
            free(slot->name);

      CompWindow *w = slot->window;
      CompScreen *s = w->screen;
      GROUP_WINDOW(w);
      GROUP_SCREEN(s);
            
      if(slot == gs->draggedSlot)
      {
            gs->draggedSlot = NULL;
            gs->dragged = FALSE;

            if (gs->grabState == ScreenGrabTabDrag)
                  groupGrabScreen(s, ScreenGrabNone);
      }
      
      gw->slot = NULL;
      free(slot);
}

/*
 * groupCreateSlot
 *
 */
void groupCreateSlot(GroupSelection *group, CompWindow *w)
{
      if(!group->tabBar)
            return;

      GROUP_WINDOW(w);

      GroupTabBarSlot *slot = (GroupTabBarSlot*) malloc(sizeof(GroupTabBarSlot));
      slot->window = w;
      slot->name = groupGetWindowTitle(slot->window);

      slot->region = XCreateRegion();

      groupInsertTabBarSlot(group->tabBar, slot);
      gw->slot = slot;
}

#define SPRING_K  gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_SPRING_K].value.f
#define FRICTION  gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_FRICTION].value.f
#define SIZE            gs->opt[GROUP_SCREEN_OPTION_THUMB_SIZE].value.i
#define BORDER          gs->opt[GROUP_SCREEN_OPTION_BORDER_RADIUS].value.i
#define Y_START_MOVE    gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_Y_DISTANCE].value.i
#define SPEED_LIMIT     gs->opt[GROUP_SCREEN_OPTION_TAB_DRAG_SPEED_LIMIT].value.i

/*
 * groupSpringForce
 *
 */
static int groupSpringForce(CompScreen *s, int centerX, int springX)
{
      GROUP_SCREEN(s);
      
      // Each slot has a spring attached to it, starting at springX, and ending at the center of the slot (centerX).
      // The spring will cause the slot to move, using the well-known physical formula F = k * dl... 
      
      return -SPRING_K * (centerX - springX);
}

/*
 * groupDraggedSlotForce
 *
 */
static int groupDraggedSlotForce(CompScreen *s, int distanceX, int distanceY)
{
      // The dragged slot will make the slot move, to get DnD animations (slots will make room for the newly inserted slot).
      // As the dragged slot is closer to the slot, it will put more force on the slot, causing it to make room for the dragged slot...
      // But if the dragged slot gets too close to the slot, they are going to be reordered soon, so the force will get lower.
      
      // If the dragged slot is in the other side of the slot, it will have to make force in the opposite direction.
      
      // So we the needed funtion is an odd function that goes up at first, and down after that.
      // Sinus is a function like that... :)
      
      // The maximum is got when x = (x1 + x2) / 2, in this case: x = SIZE + BORDER.
      // Because of that, for x = SIZE + BORDER, we get a force of SPRING_K * (SIZE + BORDER) / 2.
      // That equals to the force we get from the the spring.
      // This way, the slot won't move when its distance from the dragged slot is SIZE + BORDER (which is the default distance between slots).

      GROUP_SCREEN(s);

      float a = SPRING_K * (SIZE + BORDER) / 2; // The maximum value.
      float b = PI /  (2 * SIZE + 2 * BORDER);  // This will make distanceX == 2 * (SIZE + BORDER) to get 0, 
                                          // and distanceX == (SIZE + BORDER) to get the maximum.
      
      // If there is some distance between the slots in the y axis, the slot should get less force...
      // For this, we change max to a lower value, using a simple linear function.
      
      if(distanceY < Y_START_MOVE)
            a *= 1.0f - (float)distanceY / Y_START_MOVE;
      else
            a = 0;
      
      if(abs(distanceX) < 2 * (SIZE + BORDER))
            return a * sin(b * distanceX);
      else
            return 0;
}

/*
 * groupApplyFriction
 *
 */
static void groupApplyFriction(CompScreen *s, int *speed)
{
      GROUP_SCREEN(s);

      if(abs(*speed) < FRICTION)
            *speed = 0;
                  
      else if(*speed > 0)
            *speed -= FRICTION;
            
      else if(*speed < 0)
            *speed += FRICTION;
}

/*
 * groupApplySpeedLimit
 *
 */
static void groupApplySpeedLimit(CompScreen *s, int *speed)
{
      GROUP_SCREEN(s);
      
      if(*speed > SPEED_LIMIT)
            *speed = SPEED_LIMIT;
      
      else if(*speed < -SPEED_LIMIT)
            *speed = - SPEED_LIMIT;
}

/*
 * groupApplyForces
 *
 */
void groupApplyForces(CompScreen *s, GroupTabBar *bar, GroupTabBarSlot* draggedSlot)
{
      GROUP_SCREEN(s);

      GroupTabBarSlot* slot, *slot2;
      int centerX, centerY;
      int draggedCenterX, draggedCenterY;

      if (draggedSlot) {
            int vx, vy;
            groupGetDrawOffsetForSlot(draggedSlot, &vx, &vy);

            draggedCenterX = ((draggedSlot->region->extents.x1 + draggedSlot->region->extents.x2) / 2) + vx;
            draggedCenterY = ((draggedSlot->region->extents.y1 + draggedSlot->region->extents.y2) / 2) + vy;
      } else {
            draggedCenterX = 0;
            draggedCenterY = 0;
      }
      
      bar->leftSpeed += groupSpringForce(s, bar->region->extents.x1, bar->leftSpringX);
      bar->rightSpeed += groupSpringForce(s, bar->region->extents.x2, bar->rightSpringX);
      
      if (draggedSlot)
      {
            int leftForce = groupDraggedSlotForce(s, bar->region->extents.x1 - SIZE / 2 - draggedCenterX,
                                          abs((bar->region->extents.y1 + bar->region->extents.y2) / 2 - draggedCenterY));
            
            int rightForce = groupDraggedSlotForce(s, bar->region->extents.x2 + SIZE / 2 - draggedCenterX,
                                          abs((bar->region->extents.y1 + bar->region->extents.y2) / 2 - draggedCenterY));
            
            if(leftForce < 0)
                  bar->leftSpeed += leftForce;
            if(rightForce > 0)
                  bar->rightSpeed += rightForce;
      }
      
      for(slot = bar->slots; slot; slot = slot->next)
      {
            centerX = (slot->region->extents.x1 + slot->region->extents.x2) / 2;
            centerY = (slot->region->extents.y1 + slot->region->extents.y2) / 2;
            
            slot->speed += groupSpringForce(s, centerX, slot->springX);
            
            if(draggedSlot && draggedSlot != slot)
            {           
                  int draggedSlotForce = groupDraggedSlotForce(s, centerX - draggedCenterX,
                                                      abs(centerY - draggedCenterY));
                  
                  slot->speed += draggedSlotForce ;
                  
                  slot2 = NULL;
                  
                  if(draggedSlotForce < 0)
                  {
                        slot2 = slot->prev;
                        bar->leftSpeed += draggedSlotForce;
                  }
                  
                  else if(draggedSlotForce > 0)
                  {
                        slot2 = slot->next;
                        bar->rightSpeed += draggedSlotForce;
                  }
                  
                  for( ; slot2; slot2 = (draggedSlotForce < 0)? slot2->prev: slot2->next)
                  {
                        if(slot2 != draggedSlot)
                              slot2->speed += draggedSlotForce;
                  }
            }
      }
      
      for(slot = bar->slots; slot; slot = slot->next)
      {
            groupApplyFriction(s, &slot->speed);
            groupApplySpeedLimit(s, &slot->speed);
      }
      
      groupApplyFriction(s, &bar->leftSpeed);
      groupApplySpeedLimit(s, &bar->leftSpeed);
      
      groupApplyFriction(s, &bar->rightSpeed);
      groupApplySpeedLimit(s, &bar->rightSpeed);
}

/*
 * groupApplySpeeds
 *
 */
void groupApplySpeeds(CompScreen* s, GroupTabBar* bar, int msSinceLastRepaint)
{
      GROUP_SCREEN(s);

      GroupTabBarSlot* slot;
      int move;
      XRectangle box;
      Bool updateTabBar = FALSE;
      
      box.x = bar->region->extents.x1;
      box.y = bar->region->extents.y1; 
      box.width = bar->region->extents.x2 - bar->region->extents.x1;
      box.height = bar->region->extents.y2 - bar->region->extents.y1;
      
      bar->leftMsSinceLastMove += msSinceLastRepaint;
      bar->rightMsSinceLastMove += msSinceLastRepaint;
      
      // Left
      move = bar->leftSpeed * bar->leftMsSinceLastMove / 1000;
      
      if(move)
      {
            box.x += move;
            box.width -= move;

            bar->leftMsSinceLastMove = 0;
            updateTabBar = TRUE;
      }
      
      else if(bar->leftSpeed == 0 && bar->region->extents.x1 != bar->leftSpringX && 
            SPRING_K * abs(bar->region->extents.x1 - bar->leftSpringX) < FRICTION)
      {
            // Friction is preventing from the left border to get to its original position.
            box.x += bar->leftSpringX - bar->region->extents.x1;
            box.width -= bar->leftSpringX - bar->region->extents.x1;

            bar->leftMsSinceLastMove = 0;
            updateTabBar = TRUE;
      }
      
      else if(bar->leftSpeed == 0)
            bar->leftMsSinceLastMove = 0;
      
      //Right
      move = bar->rightSpeed * bar->rightMsSinceLastMove / 1000;
      
      if(move)
      {
            box.width += move;
            
            bar->rightMsSinceLastMove = 0;
            updateTabBar = TRUE;
      }
      
      else if(bar->rightSpeed == 0 && bar->region->extents.x2 != bar->rightSpringX && 
            SPRING_K * abs(bar->region->extents.x2 - bar->rightSpringX) < FRICTION)
      {
            // Friction is preventing from the right border to get to its original position.
            box.width += bar->leftSpringX - bar->region->extents.x1;

            bar->leftMsSinceLastMove = 0;
            updateTabBar = TRUE;
      }
      
      else if(bar->rightSpeed == 0)
            bar->rightMsSinceLastMove = 0;
      
      if(updateTabBar)
      {
            EMPTY_REGION(bar->region);
            XUnionRectWithRegion(&box, bar->region, bar->region);
      }
      
      for(slot = bar->slots; slot; slot = slot->next)
      {
            slot->msSinceLastMove += msSinceLastRepaint;
            move = slot->speed * slot->msSinceLastMove / 1000;
            
            if(move)
            {
                  XOffsetRegion (slot->region, 
                        move, 0);
                  slot->msSinceLastMove = 0;
            }
            
            else if(slot->speed == 0 && (slot->region->extents.x1 + slot->region->extents.x2) / 2 != slot->springX &&
                  SPRING_K * abs((slot->region->extents.x1 + slot->region->extents.x2) / 2 - slot->springX) < FRICTION)
            {
                  // Friction is preventing from the slot to get to its original position.
                  
                  XOffsetRegion (slot->region,
                        slot->springX - (slot->region->extents.x1 + slot->region->extents.x2) / 2, 0);
                  slot->msSinceLastMove = 0;
            }
            
            else if(slot->speed == 0)
                  slot->msSinceLastMove = 0;
      }
}

/*
 * groupInitTabBar
 *
 */
void groupInitTabBar(GroupSelection *group, CompWindow* topTab)
{
      // error
      if (group->tabBar)
            return;

      GroupTabBar *bar = (GroupTabBar*) malloc(sizeof(GroupTabBar));
      bar->slots = NULL;
      bar->nSlots = 0;
      bar->state = PaintOff;
      bar->animationTime = 0;
      bar->timeoutHandle = 0;
      bar->textLayer = NULL;
      bar->bgLayer = NULL;
      bar->selectionLayer = NULL;
      bar->hoveredSlot = NULL;
      bar->textSlot = NULL;
      group->tabBar = bar;

      bar->region = XCreateRegion();

      int i;
      for(i = 0; i < group->nWins; i++)
            groupCreateSlot(group, group->windows[i]);
      
      groupRecalcTabBarPos(group, WIN_X(topTab) + WIN_WIDTH(topTab) / 2,
                        WIN_X(topTab), WIN_X(topTab) + WIN_WIDTH(topTab));
}

/*
 * groupDeleteTabBar
 *
 */
void groupDeleteTabBar(GroupSelection *group)
{
      GroupTabBar *bar = group->tabBar;
      
      groupDestroyCairoLayer(group->screen, bar->textLayer);
      groupDestroyCairoLayer(group->screen, bar->bgLayer);
      groupDestroyCairoLayer(group->screen, bar->selectionLayer);

      groupDestroyInputPreventionWindow(group);

      if (bar->timeoutHandle)
            compRemoveTimeout(bar->timeoutHandle);

      while (bar->slots)
            groupDeleteTabBarSlot(bar, bar->slots);

      if (bar->region)
            XDestroyRegion(bar->region);

      free(bar);
      group->tabBar = NULL;
}

/*
 * groupInitTab
 *
 */
Bool
groupInitTab(CompDisplay * d, CompAction * action, CompActionState state,
           CompOption * option, int nOption)
{
      CompWindow *w = findWindowAtDisplay(d, d->activeWindow);
      Bool allowUntab = TRUE;
      
      if (!w)
            return TRUE;

      GROUP_WINDOW(w);
      
      if (gw->inSelection)
      {
            groupGroupWindows(d, action, state, option, nOption);
            allowUntab = FALSE;     // If the window was selected, we don't want to untab the group.
                              // because the user probably wanted to tab the selected windows.
      }
      
      if (!gw->group)
            return TRUE;
      
      if (gw->group->tabbingState != PaintOff)
            groupSyncWindows(gw->group);

      if (!gw->group->tabBar) {
            groupTabGroup(w);
      } else if (allowUntab) {
            groupUntabGroup(gw->group);
      }

      
      damageScreen(w->screen);
      
      return TRUE;
}

/*
 * groupChangeTabLeft
 *
 */
Bool
groupChangeTabLeft(CompDisplay * d, CompAction * action, CompActionState state,
             CompOption * option, int nOption)
{
      CompWindow *w = findWindowAtDisplay(d, d->activeWindow);
      CompWindow *topTab = w;
      
      if(!w)
            return TRUE;
      
      GROUP_WINDOW(w);
      GROUP_SCREEN(w->screen);
      
      if(!gw->slot || !gw->group)
            return TRUE;
      
      if(gw->group->nextTopTab)
            topTab = NEXT_TOP_TAB(gw->group);
      else if(gw->group->topTab)
            topTab = TOP_TAB(gw->group);  //If there are no tabbing animations, topTab is never NULL.
      
      gw = GET_GROUP_WINDOW(topTab, gs);

      if(gw->slot->prev)
            return groupChangeTab(gw->slot->prev, RotateLeft);
      else
            return groupChangeTab(gw->group->tabBar->revSlots, RotateLeft);
}

/*
 * groupChangeTabRight
 *
 */
Bool
groupChangeTabRight(CompDisplay * d, CompAction * action, CompActionState state,
             CompOption * option, int nOption)
{
      CompWindow *w = findWindowAtDisplay(d, d->activeWindow);
      CompWindow *topTab = w;
      
      if(!w)
            return TRUE;
      
      GROUP_WINDOW(w);
      GROUP_SCREEN(w->screen);
      
      if(!gw->slot || !gw->group)
            return TRUE;
      
      if(gw->group->nextTopTab)
            topTab = NEXT_TOP_TAB(gw->group);
      else if(gw->group->topTab)
            topTab = TOP_TAB(gw->group);  //If there are no tabbing animations, topTab is never NULL.
      
      gw = GET_GROUP_WINDOW(topTab, gs);

      if(gw->slot->next)
            return groupChangeTab(gw->slot->next, RotateRight);
      else
            return groupChangeTab(gw->group->tabBar->slots, RotateRight);
}

/*
 * groupSwitchTopTabInput
 *
 */
void
groupSwitchTopTabInput(GroupSelection *group, Bool enable)
{
      if(!group->tabBar || !HAS_TOP_WIN(group))
            return;

      if (!group->inputPrevention)
            groupCreateInputPreventionWindow(group);

      if (!enable) 
            XMapWindow(group->screen->display->display, group->inputPrevention);
      else 
            XUnmapWindow(group->screen->display->display, group->inputPrevention);
}

/*
 * groupUpdateInputPreventionWindow
 *
 */
void
groupUpdateInputPreventionWindow(GroupSelection *group)
{
      XWindowChanges xwc; 
      
      if(!group->tabBar || !HAS_TOP_WIN(group))
            return;

      if (!group->inputPrevention)
            groupCreateInputPreventionWindow(group);

      CompWindow *w = TOP_TAB(group);

      xwc.x = group->tabBar->leftSpringX;
      xwc.y = group->tabBar->region->extents.y1; 
      xwc.width = group->tabBar->rightSpringX - group->tabBar->leftSpringX;
      xwc.height = group->tabBar->region->extents.y2 - group->tabBar->region->extents.y1; 

      xwc.stack_mode = Above;
      
      xwc.sibling = w->id;

      XConfigureWindow(group->screen->display->display, group->inputPrevention, 
                  CWSibling | CWStackMode | CWX | CWY | CWWidth | CWHeight, &xwc); 
}

/*
 * groupCreateInputPreventionWindow
 *
 */
void groupCreateInputPreventionWindow(GroupSelection* group)
{
      if (!group->inputPrevention)    //For preventing a memory leak. 
      { 
            XSetWindowAttributes attrib; 
            attrib.override_redirect = TRUE;
            attrib.event_mask = ButtonPressMask;

            group->inputPrevention = XCreateWindow(group->screen->display->display, 
                        group->screen->root, -100, -100, 1, 1, 0, CopyFromParent,  
                        InputOnly, CopyFromParent, CWOverrideRedirect | CWEventMask, &attrib); 
      } 
}

/*
 * groupDestroyInputPreventionWindow
 *
 */
void groupDestroyInputPreventionWindow(GroupSelection* group)
{
      if(group->inputPrevention) 
      { 
            XDestroyWindow(group->screen->display->display, group->inputPrevention); 

            group->inputPrevention = None; 
      }
//
//    if (group->tabBar)
//          group->tabBar->lastHoveredSlot = NULL; 
} 



Generated by  Doxygen 1.6.0   Back to index