1 /**
2  *
3  * Compiz group plugin
4  *
5  * tab.c
6  *
7  * Copyright : (C) 2006-2007 by Patrick Niklaus, Roi Cohen, Danny Baumann
8  * Authors: Patrick Niklaus <patrick.niklaus@googlemail.com>
9  *          Roi Cohen       <roico.beryl@gmail.com>
10  *          Danny Baumann   <maniac@opencompositing.org>
11  *
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  **/
24 
25 #include "group-internal.h"
26 
27 /*
28  * groupGetCurrentMousePosition
29  *
30  * Description:
31  * Return the current function of the pointer at the given screen.
32  * The position is queried trough XQueryPointer directly from the xserver.
33  *
34  */
35 Bool
groupGetCurrentMousePosition(CompScreen * s,int * x,int * y)36 groupGetCurrentMousePosition (CompScreen *s,
37 			      int        *x,
38 			      int        *y)
39 {
40     unsigned int rmask;
41     int          mouseX, mouseY, winX, winY;
42     Window       root;
43     Window       child;
44     Bool         result;
45 
46     result = XQueryPointer (s->display->display, s->root, &root,
47 			    &child, &mouseX, &mouseY, &winX, &winY, &rmask);
48 
49     if (result)
50     {
51 	(*x) = mouseX;
52 	(*y) = mouseY;
53     }
54 
55     return result;
56 }
57 
58 /*
59  * groupGetClippingRegion
60  *
61  * Description:
62  * This function returns a clipping region which is used to clip
63  * several events involving window stack such as hover detection
64  * in the tab bar or Drag'n'Drop. It creates the clipping region
65  * with getting the region of every window above the given window
66  * and then adds this region to the clipping region using
67  * XUnionRectWithRegion. w->region won't work since it doesn't include
68  * the window decoration.
69  *
70  */
71 Region
groupGetClippingRegion(CompWindow * w)72 groupGetClippingRegion (CompWindow *w)
73 {
74     CompWindow *cw;
75     Region     clip;
76 
77     clip = XCreateRegion ();
78     if (!clip)
79 	return NULL;
80 
81     for (cw = w->next; cw; cw = cw->next)
82     {
83 	if (!cw->invisible && !(cw->state & CompWindowStateHiddenMask))
84 	{
85 	    XRectangle rect;
86 	    Region     buf;
87 
88 	    buf = XCreateRegion ();
89 	    if (!buf)
90 	    {
91 		XDestroyRegion (clip);
92 		return NULL;
93 	    }
94 
95 	    rect.x      = WIN_REAL_X (cw);
96 	    rect.y      = WIN_REAL_Y (cw);
97 	    rect.width  = WIN_REAL_WIDTH (cw);
98 	    rect.height = WIN_REAL_HEIGHT (cw);
99 	    XUnionRectWithRegion (&rect, buf, buf);
100 
101 	    XUnionRegion (clip, buf, clip);
102 	    XDestroyRegion (buf);
103 	}
104     }
105 
106     return clip;
107 }
108 
109 
110 /*
111  * groupClearWindowInputShape
112  *
113  */
114 void
groupClearWindowInputShape(CompWindow * w,GroupWindowHideInfo * hideInfo)115 groupClearWindowInputShape (CompWindow          *w,
116 			    GroupWindowHideInfo *hideInfo)
117 {
118     XRectangle  *rects;
119     int         count = 0, ordering;
120     CompDisplay *d = w->screen->display;
121 
122     rects = XShapeGetRectangles (d->display, w->id, ShapeInput,
123 				 &count, &ordering);
124 
125     if (count == 0)
126 	return;
127 
128     /* check if the returned shape exactly matches the window shape -
129        if that is true, the window currently has no set input shape */
130     if ((count == 1) &&
131 	(rects[0].x == -w->serverBorderWidth) &&
132 	(rects[0].y == -w->serverBorderWidth) &&
133 	(rects[0].width == (w->serverWidth + w->serverBorderWidth)) &&
134 	(rects[0].height == (w->serverHeight + w->serverBorderWidth)))
135     {
136 	count = 0;
137     }
138 
139     if (hideInfo->inputRects)
140 	XFree (hideInfo->inputRects);
141 
142     hideInfo->inputRects = rects;
143     hideInfo->nInputRects = count;
144     hideInfo->inputRectOrdering = ordering;
145 
146     XShapeSelectInput (d->display, w->id, NoEventMask);
147 
148     XShapeCombineRectangles (d->display, w->id, ShapeInput, 0, 0,
149 			     NULL, 0, ShapeSet, 0);
150 
151     XShapeSelectInput (d->display, w->id, ShapeNotify);
152 }
153 
154 /*
155  * groupSetWindowVisibility
156  *
157  */
158 void
groupSetWindowVisibility(CompWindow * w,Bool visible)159 groupSetWindowVisibility (CompWindow *w,
160 			  Bool       visible)
161 {
162     CompDisplay *d = w->screen->display;
163 
164     GROUP_WINDOW (w);
165 
166     if (!visible && !gw->windowHideInfo)
167     {
168 	GroupWindowHideInfo *info;
169 
170 	gw->windowHideInfo = info = malloc (sizeof (GroupWindowHideInfo));
171 	if (!gw->windowHideInfo)
172 	    return;
173 
174 	info->inputRects = NULL;
175 	info->nInputRects = 0;
176 	info->shapeMask = XShapeInputSelected (d->display, w->id);
177 	groupClearWindowInputShape (w, info);
178 
179 	if (w->frame)
180 	{
181 	    info->frameWindow = w->frame;
182 	    XUnmapWindow (d->display, w->frame);
183 	} else
184 	    info->frameWindow = None;
185 
186 	info->skipState = w->state & (CompWindowStateSkipPagerMask |
187 				      CompWindowStateSkipTaskbarMask);
188 
189 	changeWindowState (w, w->state |
190 			   CompWindowStateSkipPagerMask |
191 			   CompWindowStateSkipTaskbarMask);
192     }
193     else if (visible && gw->windowHideInfo)
194     {
195 	GroupWindowHideInfo *info = gw->windowHideInfo;
196 
197 	if (info->nInputRects)
198 	{
199 	    XShapeCombineRectangles (d->display, w->id, ShapeInput, 0, 0,
200 				     info->inputRects, info->nInputRects,
201 				     ShapeSet, info->inputRectOrdering);
202 	}
203 	else
204 	{
205 	    XShapeCombineMask (d->display, w->id, ShapeInput,
206 			       0, 0, None, ShapeSet);
207 	}
208 
209 	if (info->inputRects)
210 	    XFree (info->inputRects);
211 
212 	XShapeSelectInput (d->display, w->id, info->shapeMask);
213 
214 	if (info->frameWindow)
215 	{
216 	    if (w->attrib.map_state != IsUnmapped)
217 		XMapWindow (d->display, w->frame);
218 	}
219 
220 	changeWindowState (w,
221 			   (w->state & ~(CompWindowStateSkipPagerMask |
222 					 CompWindowStateSkipTaskbarMask)) |
223 			   info->skipState);
224 
225 	free (info);
226 	gw->windowHideInfo = NULL;
227     }
228 }
229 
230 /*
231  * groupTabBarTimeout
232  *
233  * Description:
234  * This function is called when the time expired (== timeout).
235  * We use this to realize a delay with the bar hiding after tab change.
236  * groupHandleAnimation sets up a timer after the animation has finished.
237  * This function itself basically just sets the tab bar to a PaintOff status
238  * through calling groupSetTabBarVisibility.
239  * The PERMANENT mask allows you to force hiding even of
240  * PaintPermanentOn tab bars.
241  *
242  */
243 static Bool
groupTabBarTimeout(void * data)244 groupTabBarTimeout (void *data)
245 {
246     GroupSelection *group = (GroupSelection *) data;
247 
248     groupTabSetVisibility (group, FALSE, PERMANENT);
249 
250     group->tabBar->timeoutHandle = 0;
251 
252     return FALSE;	/* This will free the timer. */
253 }
254 
255 /*
256  * groupShowDelayTimeout
257  *
258  */
259 static Bool
groupShowDelayTimeout(void * data)260 groupShowDelayTimeout (void *data)
261 {
262     int            mouseX, mouseY;
263     GroupSelection *group = (GroupSelection *) data;
264     CompScreen     *s = group->screen;
265     CompWindow     *topTab;
266 
267     GROUP_SCREEN (s);
268 
269     if (!HAS_TOP_WIN (group))
270     {
271 	gs->showDelayTimeoutHandle = 0;
272 	return FALSE;	/* This will free the timer. */
273     }
274 
275     topTab = TOP_TAB (group);
276 
277     groupGetCurrentMousePosition (s, &mouseX, &mouseY);
278 
279     groupRecalcTabBarPos (group, mouseX, WIN_REAL_X (topTab),
280 			  WIN_REAL_X (topTab) + WIN_REAL_WIDTH (topTab));
281 
282     groupTabSetVisibility (group, TRUE, 0);
283 
284     gs->showDelayTimeoutHandle = 0;
285     return FALSE;	/* This will free the timer. */
286 }
287 
288 /*
289  * groupTabSetVisibility
290  *
291  * Description:
292  * This function is used to set the visibility of the tab bar.
293  * The "visibility" is indicated through the PaintState, which
294  * can be PaintOn, PaintOff, PaintFadeIn, PaintFadeOut
295  * and PaintPermantOn.
296  * Currently the mask paramater is mostely used for the PERMANENT mask.
297  * This mask affects how the visible parameter is handled, for example if
298  * visibule is set to TRUE and the mask to PERMANENT state it will set
299  * PaintPermanentOn state for the tab bar. When visibile is FALSE, mask 0
300  * and the current state of the tab bar is PaintPermanentOn it won't do
301  * anything because its not strong enough to disable a
302  * Permanent-State, for those you need the mask.
303  *
304  */
305 void
groupTabSetVisibility(GroupSelection * group,Bool visible,unsigned int mask)306 groupTabSetVisibility (GroupSelection *group,
307 		       Bool           visible,
308 		       unsigned int   mask)
309 {
310     GroupTabBar *bar;
311     CompWindow  *topTab;
312     PaintState  oldState;
313     CompScreen  *s;
314 
315     if (!group || !group->windows || !group->tabBar || !HAS_TOP_WIN (group))
316 	return;
317 
318     s = group->screen;
319     bar = group->tabBar;
320     topTab = TOP_TAB (group);
321     oldState = bar->state;
322 
323     /* hide tab bars for invisible top windows */
324     if ((topTab->state & CompWindowStateHiddenMask) || topTab->invisible)
325     {
326 	bar->state = PaintOff;
327 	groupSwitchTopTabInput (group, TRUE);
328     }
329     else if (visible && bar->state != PaintPermanentOn && (mask & PERMANENT))
330     {
331 	bar->state = PaintPermanentOn;
332 	groupSwitchTopTabInput (group, FALSE);
333     }
334     else if (visible && bar->state == PaintPermanentOn && !(mask & PERMANENT))
335     {
336 	bar->state = PaintOn;
337     }
338     else if (visible && (bar->state == PaintOff || bar->state == PaintFadeOut))
339     {
340 	if (groupGetBarAnimations (s))
341 	{
342 	    bar->bgAnimation = AnimationReflex;
343 	    bar->bgAnimationTime = groupGetReflexTime (s) * 1000.0;
344 	}
345 	bar->state = PaintFadeIn;
346 	groupSwitchTopTabInput (group, FALSE);
347     }
348     else if (!visible &&
349 	     (bar->state != PaintPermanentOn || (mask & PERMANENT)) &&
350 	     (bar->state == PaintOn || bar->state == PaintPermanentOn ||
351 	      bar->state == PaintFadeIn))
352     {
353 	bar->state = PaintFadeOut;
354 	groupSwitchTopTabInput (group, TRUE);
355     }
356 
357     if (bar->state == PaintFadeIn || bar->state == PaintFadeOut)
358 	bar->animationTime = (groupGetFadeTime (s) * 1000) - bar->animationTime;
359 
360     if (bar->state != oldState)
361 	groupDamageTabBarRegion (group);
362 }
363 
364 /*
365  * groupGetDrawOffsetForSlot
366  *
367  * Description:
368  * Its used when the draggedSlot is dragged to another viewport.
369  * It calculates a correct offset to the real slot position.
370  *
371  */
372 void
groupGetDrawOffsetForSlot(GroupTabBarSlot * slot,int * hoffset,int * voffset)373 groupGetDrawOffsetForSlot (GroupTabBarSlot *slot,
374 			   int             *hoffset,
375 			   int             *voffset)
376 {
377     CompWindow *w, *topTab;
378     CompScreen *s;
379     int        vx, vy, x, y;
380 
381     if (!slot || !slot->window)
382 	return;
383 
384     w = slot->window;
385     s = w->screen;
386 
387     GROUP_WINDOW (w);
388     GROUP_SCREEN (s);
389 
390     if (slot != gs->draggedSlot)
391     {
392 	if (hoffset)
393 	    *hoffset = 0;
394 	if (voffset)
395 	    *voffset = 0;
396 
397 	return;
398     }
399 
400     if (HAS_TOP_WIN (gw->group))
401 	topTab = TOP_TAB (gw->group);
402     else if (HAS_PREV_TOP_WIN (gw->group))
403 	topTab = PREV_TOP_TAB (gw->group);
404     else
405     {
406 	if (hoffset)
407 	    *hoffset = 0;
408 	if (voffset)
409 	    *voffset = 0;
410 	return;
411     }
412 
413     x = WIN_CENTER_X (topTab) - WIN_WIDTH (w) / 2;
414     y = WIN_CENTER_Y (topTab) - WIN_HEIGHT (w) / 2;
415 
416     viewportForGeometry (s, x, y, w->serverWidth, w->serverHeight,
417 			 w->serverBorderWidth, &vx, &vy);
418 
419     if (hoffset)
420 	*hoffset = ((s->x - vx) % s->hsize) * s->width;
421 
422     if (voffset)
423 	*voffset = ((s->y - vy) % s->vsize) * s->height;
424 }
425 
426 /*
427  * groupHandleHoverDetection
428  *
429  * Description:
430  * This function is called from groupPreparePaintScreen to handle
431  * the hover detection. This is needed for the text showing up,
432  * when you hover a thumb on the thumb bar.
433  *
434  * FIXME: we should better have a timer for that ...
435  */
436 void
groupHandleHoverDetection(GroupSelection * group)437 groupHandleHoverDetection (GroupSelection *group)
438 {
439     GroupTabBar *bar = group->tabBar;
440     CompWindow  *topTab = TOP_TAB (group);
441     int         mouseX, mouseY;
442     Bool        mouseOnScreen, inLastSlot;
443 
444     /* first get the current mouse position */
445     mouseOnScreen = groupGetCurrentMousePosition (group->screen,
446 						  &mouseX, &mouseY);
447 
448     if (!mouseOnScreen)
449 	return;
450 
451     /* then check if the mouse is in the last hovered slot --
452        this saves a lot of CPU usage */
453     inLastSlot = bar->hoveredSlot &&
454 	         XPointInRegion (bar->hoveredSlot->region, mouseX, mouseY);
455 
456     if (!inLastSlot)
457     {
458 	Region          clip;
459 	GroupTabBarSlot *slot;
460 
461 	bar->hoveredSlot = NULL;
462 	clip = groupGetClippingRegion (topTab);
463 
464 	for (slot = bar->slots; slot; slot = slot->next)
465 	{
466 	    /* We need to clip the slot region with the clip region first.
467 	       This is needed to respect the window stack, so if a window
468 	       covers a port of that slot, this part won't be used
469 	       for in-slot-detection. */
470 	    Region reg = XCreateRegion();
471 	    if (!reg)
472 	    {
473 		XDestroyRegion(clip);
474 		return;
475 	    }
476 
477 	    XSubtractRegion (slot->region, clip, reg);
478 
479 	    if (XPointInRegion (reg, mouseX, mouseY))
480 	    {
481 		bar->hoveredSlot = slot;
482 		XDestroyRegion (reg);
483 		break;
484 	    }
485 
486 	    XDestroyRegion (reg);
487 	}
488 
489 	XDestroyRegion (clip);
490 
491 	if (bar->textLayer)
492 	{
493 	    /* trigger a FadeOut of the text */
494 	    if ((bar->hoveredSlot != bar->textSlot) &&
495 		(bar->textLayer->state == PaintFadeIn ||
496 		 bar->textLayer->state == PaintOn))
497 	    {
498 		bar->textLayer->animationTime =
499 		    (groupGetFadeTextTime (group->screen) * 1000) -
500 		    bar->textLayer->animationTime;
501 		bar->textLayer->state = PaintFadeOut;
502 	    }
503 
504 	    /* or trigger a FadeIn of the text */
505 	    else if (bar->textLayer->state == PaintFadeOut &&
506 		     bar->hoveredSlot == bar->textSlot && bar->hoveredSlot)
507 	    {
508 		bar->textLayer->animationTime =
509 		    (groupGetFadeTextTime (group->screen) * 1000) -
510 		    bar->textLayer->animationTime;
511 		bar->textLayer->state = PaintFadeIn;
512 	    }
513 	}
514     }
515 }
516 
517 /*
518  * groupHandleTabBarFade
519  *
520  * Description:
521  * This function is called from groupPreparePaintScreen
522  * to handle the tab bar fade. It checks the animationTime and updates it,
523  * so we can calculate the alpha of the tab bar in the painting code with it.
524  *
525  */
526 void
groupHandleTabBarFade(GroupSelection * group,int msSinceLastPaint)527 groupHandleTabBarFade (GroupSelection *group,
528 		       int            msSinceLastPaint)
529 {
530     GroupTabBar *bar = group->tabBar;
531 
532     bar->animationTime -= msSinceLastPaint;
533 
534     if (bar->animationTime < 0)
535 	bar->animationTime = 0;
536 
537     /* Fade finished */
538     if (bar->animationTime == 0)
539     {
540 	if (bar->state == PaintFadeIn)
541 	{
542 	    bar->state = PaintOn;
543 	}
544 	else if (bar->state == PaintFadeOut)
545 	{
546 	    bar->state = PaintOff;
547 
548 	    if (bar->textLayer)
549 	    {
550 		/* Tab-bar is no longer painted, clean up
551 		   text animation variables. */
552 		bar->textLayer->animationTime = 0;
553 		bar->textLayer->state = PaintOff;
554 		bar->textSlot = bar->hoveredSlot = NULL;
555 
556 		groupRenderWindowTitle (group);
557 	    }
558 	}
559     }
560 }
561 
562 /*
563  * groupHandleTextFade
564  *
565  * Description:
566  * This function is called from groupPreparePaintScreen
567  * to handle the text fade. It checks the animationTime and updates it,
568  * so we can calculate the alpha of the text in the painting code with it.
569  *
570  */
571 void
groupHandleTextFade(GroupSelection * group,int msSinceLastPaint)572 groupHandleTextFade (GroupSelection *group,
573 		     int            msSinceLastPaint)
574 {
575     GroupTabBar     *bar = group->tabBar;
576     GroupCairoLayer *textLayer = bar->textLayer;
577 
578     /* Fade in progress... */
579     if ((textLayer->state == PaintFadeIn || textLayer->state == PaintFadeOut) &&
580 	textLayer->animationTime > 0)
581     {
582 	textLayer->animationTime -= msSinceLastPaint;
583 
584 	if (textLayer->animationTime < 0)
585 	    textLayer->animationTime = 0;
586 
587 	/* Fade has finished. */
588 	if (textLayer->animationTime == 0)
589 	{
590 	    if (textLayer->state == PaintFadeIn)
591 		textLayer->state = PaintOn;
592 
593 	    else if (textLayer->state == PaintFadeOut)
594 		textLayer->state = PaintOff;
595 	}
596     }
597 
598     if (textLayer->state == PaintOff && bar->hoveredSlot)
599     {
600 	/* Start text animation for the new hovered slot. */
601 	bar->textSlot = bar->hoveredSlot;
602 	textLayer->state = PaintFadeIn;
603 	textLayer->animationTime =
604 	    (groupGetFadeTextTime (group->screen) * 1000);
605 
606 	groupRenderWindowTitle (group);
607     }
608 
609     else if (textLayer->state == PaintOff && bar->textSlot)
610     {
611 	/* Clean Up. */
612 	bar->textSlot = NULL;
613 	groupRenderWindowTitle (group);
614     }
615 }
616 
617 /*
618  * groupHandleTabBarAnimation
619  *
620  * Description: Handles the different animations for the tab bar defined in
621  * GroupAnimationType. Basically that means this function updates
622  * tabBar->animation->time as well as checking if the animation is already
623  * finished.
624  *
625  */
626 void
groupHandleTabBarAnimation(GroupSelection * group,int msSinceLastPaint)627 groupHandleTabBarAnimation (GroupSelection *group,
628 			    int            msSinceLastPaint)
629 {
630     GroupTabBar *bar = group->tabBar;
631 
632     bar->bgAnimationTime -= msSinceLastPaint;
633 
634     if (bar->bgAnimationTime <= 0)
635     {
636 	bar->bgAnimationTime = 0;
637 	bar->bgAnimation = 0;
638 
639 	groupRenderTabBarBackground (group);
640     }
641 }
642 
643 /*
644  * groupTabChangeActivateEvent
645  *
646  * Description: Creates a compiz event to let other plugins know about
647  * the starting and ending point of the tab changing animation
648  */
649 static void
groupTabChangeActivateEvent(CompScreen * s,Bool activating)650 groupTabChangeActivateEvent (CompScreen *s,
651 			     Bool	    activating)
652 {
653     CompOption o[2];
654 
655     o[0].type = CompOptionTypeInt;
656     o[0].name = "root";
657     o[0].value.i = s->root;
658 
659     o[1].type = CompOptionTypeBool;
660     o[1].name = "active";
661     o[1].value.b = activating;
662 
663     (*s->display->handleCompizEvent) (s->display,
664 				      "group", "tabChangeActivate", o, 2);
665 }
666 
667 /*
668  * groupHandleAnimation
669  *
670  * Description:
671  * This function handles the change animation. It's called
672  * from groupHandleChanges. Don't let the changeState
673  * confuse you, PaintFadeIn equals with the start of the
674  * rotate animation and PaintFadeOut is the end of these
675  * animation.
676  *
677  */
678 void
groupHandleAnimation(GroupSelection * group)679 groupHandleAnimation (GroupSelection *group)
680 {
681     CompScreen *s = group->screen;
682 
683     if (group->changeState == TabChangeOldOut)
684     {
685 	CompWindow      *top = TOP_TAB (group);
686 	Bool            activate;
687 
688 	/* recalc here is needed (for y value)! */
689 	groupRecalcTabBarPos (group,
690 			      (group->tabBar->region->extents.x1 +
691 			       group->tabBar->region->extents.x2) / 2,
692 			      WIN_REAL_X (top),
693 			      WIN_REAL_X (top) + WIN_REAL_WIDTH (top));
694 
695 	group->changeAnimationTime += groupGetChangeAnimationTime (s) * 500;
696 
697 	if (group->changeAnimationTime <= 0)
698 	    group->changeAnimationTime = 0;
699 
700 	group->changeState = TabChangeNewIn;
701 
702 	activate = !group->checkFocusAfterTabChange;
703 	if (!activate)
704 	{
705 	    CompFocusResult focus;
706 	    focus    = allowWindowFocus (top, NO_FOCUS_MASK, s->x, s->y, 0);
707 	    activate = focus == CompFocusAllowed;
708 	}
709 
710 	if (activate)
711 	    (*s->activateWindow) (top);
712 
713 	group->checkFocusAfterTabChange = FALSE;
714     }
715 
716     if (group->changeState == TabChangeNewIn &&
717 	group->changeAnimationTime <= 0)
718     {
719 	int oldChangeAnimationTime = group->changeAnimationTime;
720 
721 	groupTabChangeActivateEvent (s, FALSE);
722 
723 	if (group->prevTopTab)
724 	    groupSetWindowVisibility (PREV_TOP_TAB (group), FALSE);
725 
726 	group->prevTopTab = group->topTab;
727 	group->changeState = NoTabChange;
728 
729 	if (group->nextTopTab)
730 	{
731 	    GroupTabBarSlot *next = group->nextTopTab;
732 	    group->nextTopTab = NULL;
733 
734 	    groupChangeTab (next, group->nextDirection);
735 
736 	    if (group->changeState == TabChangeOldOut)
737 	    {
738 		/* If a new animation was started. */
739 		group->changeAnimationTime += oldChangeAnimationTime;
740 	    }
741 	}
742 
743 	if (group->changeAnimationTime <= 0)
744 	{
745 	    group->changeAnimationTime = 0;
746 	}
747 	else if (groupGetVisibilityTime (s) != 0.0f &&
748 		 group->changeState == NoTabChange)
749 	{
750 	    groupTabSetVisibility (group, TRUE,
751 				   PERMANENT | SHOW_BAR_INSTANTLY_MASK);
752 
753 	    if (group->tabBar->timeoutHandle)
754 		compRemoveTimeout (group->tabBar->timeoutHandle);
755 
756 	    group->tabBar->timeoutHandle =
757 		compAddTimeout (groupGetVisibilityTime (s) * 1000,
758 				groupGetVisibilityTime (s) * 1200,
759 				groupTabBarTimeout, group);
760 	}
761     }
762 }
763 
764 /* adjust velocity for each animation step (adapted from the scale plugin) */
765 static int
adjustTabVelocity(CompWindow * w)766 adjustTabVelocity (CompWindow *w)
767 {
768     float dx, dy, adjust, amount;
769     float x1, y1;
770 
771     GROUP_WINDOW (w);
772 
773     x1 = gw->destination.x;
774     y1 = gw->destination.y;
775 
776     dx = x1 - (gw->orgPos.x + gw->tx);
777     adjust = dx * 0.15f;
778     amount = fabs (dx) * 1.5f;
779     if (amount < 0.5f)
780 	amount = 0.5f;
781     else if (amount > 5.0f)
782 	amount = 5.0f;
783 
784     gw->xVelocity = (amount * gw->xVelocity + adjust) / (amount + 1.0f);
785 
786     dy = y1 - (gw->orgPos.y + gw->ty);
787     adjust = dy * 0.15f;
788     amount = fabs (dy) * 1.5f;
789     if (amount < 0.5f)
790 	amount = 0.5f;
791     else if (amount > 5.0f)
792 	amount = 5.0f;
793 
794     gw->yVelocity = (amount * gw->yVelocity + adjust) / (amount + 1.0f);
795 
796     if (fabs (dx) < 0.1f && fabs (gw->xVelocity) < 0.2f &&
797 	fabs (dy) < 0.1f && fabs (gw->yVelocity) < 0.2f)
798     {
799 	gw->xVelocity = gw->yVelocity = 0.0f;
800 	gw->tx = x1 - w->serverX;
801 	gw->ty = y1 - w->serverY;
802 
803 	return 0;
804     }
805     return 1;
806 }
807 
808 static void
groupFinishTabbing(GroupSelection * group)809 groupFinishTabbing (GroupSelection *group)
810 {
811     CompScreen *s = group->screen;
812     int        i;
813 
814     GROUP_SCREEN (s);
815 
816     group->tabbingState = NoTabbing;
817     groupTabChangeActivateEvent (s, FALSE);
818 
819     if (group->tabBar)
820     {
821 	/* tabbing case - hide all non-toptab windows */
822 	GroupTabBarSlot *slot;
823 
824 	for (slot = group->tabBar->slots; slot; slot = slot->next)
825 	{
826 	    CompWindow *w = slot->window;
827 	    if (!w)
828 		continue;
829 
830 	    GROUP_WINDOW (w);
831 
832 	    if (slot == group->topTab || (gw->animateState & IS_UNGROUPING))
833 		continue;
834 
835 	    groupSetWindowVisibility (w, FALSE);
836 	}
837 	group->prevTopTab = group->topTab;
838     }
839 
840     for (i = 0; i < group->nWins; i++)
841     {
842 	CompWindow *w = group->windows[i];
843 	GROUP_WINDOW (w);
844 
845 	/* move window to target position */
846 	gs->queued = TRUE;
847 	moveWindow (w, gw->destination.x - WIN_X (w),
848 		    gw->destination.y - WIN_Y (w), TRUE, TRUE);
849 	gs->queued = FALSE;
850 	syncWindowPosition (w);
851 
852 	if (group->ungroupState == UngroupSingle &&
853 	    (gw->animateState & IS_UNGROUPING))
854 	{
855 	    groupRemoveWindowFromGroup (w);
856 	}
857 
858 	gw->animateState = 0;
859 	gw->tx = gw->ty = gw->xVelocity = gw->yVelocity = 0.0f;
860     }
861 
862     if (group->ungroupState == UngroupAll)
863 	groupDeleteGroup (group);
864     else
865 	group->ungroupState = UngroupNone;
866 }
867 
868 /*
869  * groupDrawTabAnimation
870  *
871  * Description:
872  * This function is called from groupPreparePaintScreen, to move
873  * all the animated windows, with the required animation step.
874  * The function goes through all grouped animated windows, calculates
875  * the required step using adjustTabVelocity, moves the window,
876  * and then checks if the animation is finished for that window.
877  *
878  */
879 void
groupDrawTabAnimation(GroupSelection * group,int msSinceLastPaint)880 groupDrawTabAnimation (GroupSelection *group,
881 		       int            msSinceLastPaint)
882 {
883     int        steps, i;
884     float      amount, chunk;
885     Bool       doTabbing;
886     CompScreen *s = group->screen;
887 
888     amount = msSinceLastPaint * 0.05f * groupGetTabbingSpeed (s);
889     steps = amount / (0.5f * groupGetTabbingTimestep (s));
890     if (!steps)
891 	steps = 1;
892     chunk = amount / (float)steps;
893 
894     while (steps--)
895     {
896 	doTabbing = FALSE;
897 
898 	for (i = 0; i < group->nWins; i++)
899 	{
900 	    CompWindow *cw = group->windows[i];
901 	    if (!cw)
902 		continue;
903 
904 	    GROUP_WINDOW (cw);
905 
906 	    if (!(gw->animateState & IS_ANIMATED))
907 		continue;
908 
909 	    if (!adjustTabVelocity (cw))
910 	    {
911 		gw->animateState |= FINISHED_ANIMATION;
912 		gw->animateState &= ~IS_ANIMATED;
913 	    }
914 
915 	    gw->tx += gw->xVelocity * chunk;
916 	    gw->ty += gw->yVelocity * chunk;
917 
918 	    doTabbing |= (gw->animateState & IS_ANIMATED);
919 	}
920 
921 	if (!doTabbing)
922 	{
923 	    /* tabbing animation finished */
924 	    groupFinishTabbing (group);
925 	    break;
926 	}
927     }
928 }
929 
930 /*
931  * groupUpdateTabBars
932  *
933  * Description:
934  * This function is responsible for showing / unshowing the tab-bars,
935  * when the title-bars / tab-bars are hovered.
936  * The function is called whenever a new window is entered,
937  * checks if the entered window is a window frame (and if the title
938  * bar part of that frame was hovered) or if it was the input
939  * prevention window of a tab bar, and sets tab-bar visibility
940  * according to that.
941  *
942  */
943 void
groupUpdateTabBars(CompScreen * s,Window enteredWin)944 groupUpdateTabBars (CompScreen *s,
945 		    Window     enteredWin)
946 {
947     CompWindow     *w = NULL;
948     GroupSelection *hoveredGroup = NULL;
949 
950     GROUP_SCREEN (s);
951 
952     /* do nothing if the screen is grabbed, as the frame might be drawn
953        transformed */
954     if (!otherScreenGrabExist (s, "group", "group-drag", NULL))
955     {
956 	/* first check if the entered window is a frame */
957 	for (w = s->windows; w; w = w->next)
958 	{
959 	    if (w->frame == enteredWin)
960 		break;
961 	}
962     }
963 
964     if (w)
965     {
966 	/* is the window the entered frame belongs to inside
967 	   a tabbed group? if no, it's not interesting for us */
968 	GROUP_WINDOW (w);
969 
970 	if (gw->group && gw->group->tabBar)
971 	{
972 	    int mouseX, mouseY;
973 	    /* it is grouped and tabbed, so now we have to
974 	       check if we hovered the title bar or the frame */
975 	    if (groupGetCurrentMousePosition (s, &mouseX, &mouseY))
976 	    {
977 		XRectangle rect;
978 		Region     reg = XCreateRegion();
979 		if (!reg)
980 		    return;
981 
982 		rect.x      = WIN_X (w) - w->input.left;
983 		rect.y      = WIN_Y (w) - w->input.top;
984 		rect.width  = WIN_WIDTH (w) + w->input.right;
985 		rect.height = WIN_Y (w) - rect.y;
986 		XUnionRectWithRegion (&rect, reg, reg);
987 
988 		if (XPointInRegion (reg, mouseX, mouseY))
989 		    hoveredGroup = gw->group;
990 
991 		XDestroyRegion (reg);
992 	    }
993 	}
994     }
995 
996     /* if we didn't hover a title bar, check if we hovered
997        a tab bar (means: input prevention window) */
998     if (!hoveredGroup)
999     {
1000 	GroupSelection *group;
1001 
1002 	for (group = gs->groups; group; group = group->next)
1003 	{
1004 	    if (group->inputPrevention == enteredWin)
1005 	    {
1006 		/* only accept it if the IPW is mapped */
1007 		if (group->ipwMapped)
1008 		{
1009 		    hoveredGroup = group;
1010 		    break;
1011 		}
1012 	    }
1013 	}
1014     }
1015 
1016     /* if we found a hovered tab bar different than the last one
1017        (or left a tab bar), hide the old one */
1018     if (gs->lastHoveredGroup && (hoveredGroup != gs->lastHoveredGroup))
1019 	groupTabSetVisibility (gs->lastHoveredGroup, FALSE, 0);
1020 
1021     /* if we entered a tab bar (or title bar), show the tab bar */
1022     if (hoveredGroup && HAS_TOP_WIN (hoveredGroup) &&
1023 	!TOP_TAB (hoveredGroup)->grabbed)
1024     {
1025 	GroupTabBar *bar = hoveredGroup->tabBar;
1026 
1027 	if (bar && ((bar->state == PaintOff) || (bar->state == PaintFadeOut)))
1028 	{
1029 	    int showDelayTime = groupGetTabbarShowDelay (s) * 1000;
1030 
1031 	    /* Show the tab-bar after a delay,
1032 	       only if the tab-bar wasn't fading out. */
1033 	    if (showDelayTime > 0 && (bar->state == PaintOff))
1034 	    {
1035 		if (gs->showDelayTimeoutHandle)
1036 		    compRemoveTimeout (gs->showDelayTimeoutHandle);
1037 		gs->showDelayTimeoutHandle =
1038 		    compAddTimeout (showDelayTime, (float) showDelayTime * 1.2,
1039 				    groupShowDelayTimeout, hoveredGroup);
1040 	    }
1041 	    else
1042 		groupShowDelayTimeout (hoveredGroup);
1043 	}
1044     }
1045 
1046     gs->lastHoveredGroup = hoveredGroup;
1047 }
1048 
1049 /*
1050  * groupGetConstrainRegion
1051  *
1052  */
1053 static Region
groupGetConstrainRegion(CompScreen * s)1054 groupGetConstrainRegion (CompScreen *s)
1055 {
1056     CompWindow *w;
1057     Region     region;
1058     REGION     r;
1059     int        i;
1060 
1061     region = XCreateRegion ();
1062     if (!region)
1063 	return NULL;
1064 
1065     for (i = 0;i < s->nOutputDev; i++)
1066 	XUnionRegion (&s->outputDev[i].region, region, region);
1067 
1068     r.rects    = &r.extents;
1069     r.numRects = r.size = 1;
1070 
1071     for (w = s->windows; w; w = w->next)
1072     {
1073 	if (!w->mapNum)
1074 	    continue;
1075 
1076 	if (w->struts)
1077 	{
1078 	    r.extents.x1 = w->struts->top.x;
1079 	    r.extents.y1 = w->struts->top.y;
1080 	    r.extents.x2 = r.extents.x1 + w->struts->top.width;
1081 	    r.extents.y2 = r.extents.y1 + w->struts->top.height;
1082 
1083 	    XSubtractRegion (region, &r, region);
1084 
1085 	    r.extents.x1 = w->struts->bottom.x;
1086 	    r.extents.y1 = w->struts->bottom.y;
1087 	    r.extents.x2 = r.extents.x1 + w->struts->bottom.width;
1088 	    r.extents.y2 = r.extents.y1 + w->struts->bottom.height;
1089 
1090 	    XSubtractRegion (region, &r, region);
1091 
1092 	    r.extents.x1 = w->struts->left.x;
1093 	    r.extents.y1 = w->struts->left.y;
1094 	    r.extents.x2 = r.extents.x1 + w->struts->left.width;
1095 	    r.extents.y2 = r.extents.y1 + w->struts->left.height;
1096 
1097 	    XSubtractRegion (region, &r, region);
1098 
1099 	    r.extents.x1 = w->struts->right.x;
1100 	    r.extents.y1 = w->struts->right.y;
1101 	    r.extents.x2 = r.extents.x1 + w->struts->right.width;
1102 	    r.extents.y2 = r.extents.y1 + w->struts->right.height;
1103 
1104 	    XSubtractRegion (region, &r, region);
1105 	}
1106     }
1107 
1108     return region;
1109 }
1110 
1111 /*
1112  * groupConstrainMovement
1113  *
1114  */
1115 static Bool
groupConstrainMovement(CompWindow * w,Region constrainRegion,int dx,int dy,int * new_dx,int * new_dy)1116 groupConstrainMovement (CompWindow *w,
1117 			Region     constrainRegion,
1118 			int        dx,
1119 			int        dy,
1120 			int        *new_dx,
1121 			int        *new_dy)
1122 {
1123     int status, xStatus;
1124     int origDx = dx, origDy = dy;
1125     int x, y, width, height;
1126 
1127     GROUP_WINDOW (w);
1128 
1129     if (!gw->group)
1130 	return FALSE;
1131 
1132     if (!dx && !dy)
1133 	return FALSE;
1134 
1135     x = gw->orgPos.x - w->input.left + dx;
1136     y = gw->orgPos.y - w->input.top + dy;
1137     width = WIN_REAL_WIDTH (w);
1138     height = WIN_REAL_HEIGHT (w);
1139 
1140     status = XRectInRegion (constrainRegion, x, y, width, height);
1141 
1142     xStatus = status;
1143     while (dx && (xStatus != RectangleIn))
1144     {
1145 	xStatus = XRectInRegion (constrainRegion, x, y - dy, width, height);
1146 
1147 	if (xStatus != RectangleIn)
1148 	    dx += (dx < 0) ? 1 : -1;
1149 
1150 	x = gw->orgPos.x - w->input.left + dx;
1151     }
1152 
1153     while (dy && (status != RectangleIn))
1154     {
1155 	status = XRectInRegion(constrainRegion, x, y, width, height);
1156 
1157 	if (status != RectangleIn)
1158 	    dy += (dy < 0) ? 1 : -1;
1159 
1160 	y = gw->orgPos.y - w->input.top + dy;
1161     }
1162 
1163     if (new_dx)
1164 	*new_dx = dx;
1165 
1166     if (new_dy)
1167 	*new_dy = dy;
1168 
1169     return ((dx != origDx) || (dy != origDy));
1170 }
1171 
1172 /*
1173  * groupApplyConstraining
1174  *
1175  */
1176 static void
groupApplyConstraining(GroupSelection * group,Region constrainRegion,Window constrainedWindow,int dx,int dy)1177 groupApplyConstraining (GroupSelection *group,
1178 			Region         constrainRegion,
1179 			Window         constrainedWindow,
1180 			int            dx,
1181 			int            dy)
1182 {
1183     int        i;
1184     CompWindow *w;
1185 
1186     if (!dx && !dy)
1187 	return;
1188 
1189     for (i = 0; i < group->nWins; i++)
1190     {
1191 	w = group->windows[i];
1192 	GROUP_WINDOW (w);
1193 
1194 	/* ignore certain windows: we don't want to apply the constraining
1195 	   results on the constrained window itself, nor do we want to
1196 	   change the target position of unamimated windows and of
1197 	   windows which already are constrained */
1198 	if (w->id == constrainedWindow)
1199 	    continue;
1200 
1201 	if (!(gw->animateState & IS_ANIMATED))
1202 	    continue;
1203 
1204 	if (gw->animateState & DONT_CONSTRAIN)
1205 	    continue;
1206 
1207 	if (!(gw->animateState & CONSTRAINED_X))
1208 	{
1209 	    gw->animateState |= IS_ANIMATED;
1210 
1211 	    /* applying the constraining result of another window
1212 	       might move the window offscreen, too, so check
1213 	       if this is not the case */
1214 	    if (groupConstrainMovement (w, constrainRegion, dx, 0, &dx, NULL))
1215 		gw->animateState |= CONSTRAINED_X;
1216 
1217 	    gw->destination.x += dx;
1218 	}
1219 
1220 	if (!(gw->animateState & CONSTRAINED_Y))
1221 	{
1222 	    gw->animateState |= IS_ANIMATED;
1223 
1224 	    /* analog to X case */
1225 	    if (groupConstrainMovement (w, constrainRegion, 0, dy, NULL, &dy))
1226 		gw->animateState |= CONSTRAINED_Y;
1227 
1228 	    gw->destination.y += dy;
1229 	}
1230     }
1231 }
1232 
1233 /*
1234  * groupStartTabbingAnimation
1235  *
1236  */
1237 void
groupStartTabbingAnimation(GroupSelection * group,Bool tab)1238 groupStartTabbingAnimation (GroupSelection *group,
1239 			    Bool           tab)
1240 {
1241     CompScreen *s;
1242     int        i;
1243     int        dx, dy;
1244     int        constrainStatus;
1245 
1246     if (!group || (group->tabbingState != NoTabbing))
1247 	return;
1248 
1249     s = group->screen;
1250     group->tabbingState = (tab) ? Tabbing : Untabbing;
1251     groupTabChangeActivateEvent (s, TRUE);
1252 
1253     if (!tab)
1254     {
1255 	/* we need to set up the X/Y constraining on untabbing */
1256 	Region constrainRegion = groupGetConstrainRegion (s);
1257 	Bool   constrainedWindows = TRUE;
1258 
1259 	if (!constrainRegion)
1260 	    return;
1261 
1262 	/* reset all flags */
1263 	for (i = 0; i < group->nWins; i++)
1264 	{
1265 	    GROUP_WINDOW (group->windows[i]);
1266 	    gw->animateState &= ~(CONSTRAINED_X | CONSTRAINED_Y |
1267 				  DONT_CONSTRAIN);
1268 	}
1269 
1270 	/* as we apply the constraining in a flat loop,
1271 	   we may need to run multiple times through this
1272 	   loop until all constraining dependencies are met */
1273 	while (constrainedWindows)
1274 	{
1275 	    constrainedWindows = FALSE;
1276 	    /* loop through all windows and try to constrain their
1277 	       animation path (going from gw->orgPos to
1278 	       gw->destination) to the active screen area */
1279 	    for (i = 0; i < group->nWins; i++)
1280 	    {
1281 		CompWindow *w = group->windows[i];
1282 		GROUP_WINDOW (w);
1283 
1284 		/* ignore windows which aren't animated and/or
1285 		   already are at the edge of the screen area */
1286 		if (!(gw->animateState & IS_ANIMATED))
1287 		    continue;
1288 
1289 		if (gw->animateState & DONT_CONSTRAIN)
1290 		    continue;
1291 
1292 		/* is the original position inside the screen area? */
1293 		constrainStatus = XRectInRegion (constrainRegion,
1294 						 gw->orgPos.x  - w->input.left,
1295 						 gw->orgPos.y - w->input.top,
1296 						 WIN_REAL_WIDTH (w),
1297 						 WIN_REAL_HEIGHT (w));
1298 
1299 		/* constrain the movement */
1300 		if (groupConstrainMovement (w, constrainRegion,
1301 					    gw->destination.x - gw->orgPos.x,
1302 					    gw->destination.y - gw->orgPos.y,
1303 					    &dx, &dy))
1304 		{
1305 		    /* handle the case where the window is outside the screen
1306 		       area on its whole animation path */
1307 		    if (constrainStatus != RectangleIn && !dx && !dy)
1308 		    {
1309 			gw->animateState |= DONT_CONSTRAIN;
1310 			gw->animateState |= CONSTRAINED_X | CONSTRAINED_Y;
1311 
1312 			/* use the original position as last resort */
1313 			gw->destination.x = gw->mainTabOffset.x;
1314 			gw->destination.y = gw->mainTabOffset.y;
1315 		    }
1316 		    else
1317 		    {
1318 			/* if we found a valid target position, apply
1319 			   the change also to other windows to retain
1320 			   the distance between the windows */
1321 			groupApplyConstraining (group, constrainRegion, w->id,
1322 						dx - gw->destination.x +
1323 						gw->orgPos.x,
1324 						dy - gw->destination.y +
1325 						gw->orgPos.y);
1326 
1327 			/* if we hit constraints, adjust the mask and the
1328 			   target position accordingly */
1329 			if (dx != (gw->destination.x - gw->orgPos.x))
1330 			{
1331 			    gw->animateState |= CONSTRAINED_X;
1332 			    gw->destination.x = gw->orgPos.x + dx;
1333 			}
1334 
1335 			if (dy != (gw->destination.y - gw->orgPos.y))
1336 			{
1337 			    gw->animateState |= CONSTRAINED_Y;
1338 			    gw->destination.y = gw->orgPos.y + dy;
1339 			}
1340 
1341 			constrainedWindows = TRUE;
1342 		    }
1343 		}
1344 	    }
1345 	}
1346 	XDestroyRegion (constrainRegion);
1347     }
1348 }
1349 
1350 /*
1351  * groupTabGroup
1352  *
1353  */
1354 void
groupTabGroup(CompWindow * main)1355 groupTabGroup (CompWindow *main)
1356 {
1357     GroupSelection  *group;
1358     GroupTabBarSlot *slot;
1359     CompScreen      *s = main->screen;
1360     int             width, height;
1361     int             space, thumbSize;
1362 
1363     GROUP_WINDOW (main);
1364 
1365     group = gw->group;
1366     if (!group || group->tabBar)
1367 	return;
1368 
1369     if (!s->display->shapeExtension)
1370     {
1371 	compLogMessage ("group", CompLogLevelError,
1372 			"No X shape extension! Tabbing disabled.");
1373 	return;
1374     }
1375 
1376     groupInitTabBar (group, main);
1377     if (!group->tabBar)
1378 	return;
1379 
1380     groupCreateInputPreventionWindow (group);
1381 
1382     group->tabbingState = NoTabbing;
1383     /* Slot is initialized after groupInitTabBar(group); */
1384     groupChangeTab (gw->slot, RotateUncertain);
1385     groupRecalcTabBarPos (gw->group, WIN_CENTER_X (main),
1386 			  WIN_X (main), WIN_X (main) + WIN_WIDTH (main));
1387 
1388     width = group->tabBar->region->extents.x2 -
1389 	    group->tabBar->region->extents.x1;
1390     height = group->tabBar->region->extents.y2 -
1391 	     group->tabBar->region->extents.y1;
1392 
1393     group->tabBar->textLayer = groupCreateCairoLayer (s, width, height);
1394     if (group->tabBar->textLayer)
1395     {
1396 	GroupCairoLayer *layer;
1397 
1398 	layer = group->tabBar->textLayer;
1399 	layer->state = PaintOff;
1400 	layer->animationTime = 0;
1401 	groupRenderWindowTitle (group);
1402     }
1403     if (group->tabBar->textLayer)
1404     {
1405 	GroupCairoLayer *layer;
1406 
1407 	layer = group->tabBar->textLayer;
1408 	layer->animationTime = groupGetFadeTextTime (s) * 1000;
1409 	layer->state = PaintFadeIn;
1410     }
1411 
1412     /* we need a buffer for DnD here */
1413     space = groupGetThumbSpace (s);
1414     thumbSize = groupGetThumbSize (s);
1415     group->tabBar->bgLayer = groupCreateCairoLayer (s,
1416 						    width + space + thumbSize,
1417 						    height);
1418     if (group->tabBar->bgLayer)
1419     {
1420 	group->tabBar->bgLayer->state = PaintOn;
1421 	group->tabBar->bgLayer->animationTime = 0;
1422 	groupRenderTabBarBackground (group);
1423     }
1424 
1425     width = group->topTab->region->extents.x2 -
1426 	    group->topTab->region->extents.x1;
1427     height = group->topTab->region->extents.y2 -
1428 	     group->topTab->region->extents.y1;
1429 
1430     group->tabBar->selectionLayer = groupCreateCairoLayer (s, width, height);
1431     if (group->tabBar->selectionLayer)
1432     {
1433 	group->tabBar->selectionLayer->state = PaintOn;
1434 	group->tabBar->selectionLayer->animationTime = 0;
1435 	groupRenderTopTabHighlight (group);
1436     }
1437 
1438     if (!HAS_TOP_WIN (group))
1439 	return;
1440 
1441     for (slot = group->tabBar->slots; slot; slot = slot->next)
1442     {
1443 	CompWindow *cw = slot->window;
1444 
1445 	GROUP_WINDOW (cw);
1446 
1447 	if (gw->animateState & (IS_ANIMATED | FINISHED_ANIMATION))
1448 	    moveWindow (cw,
1449 			gw->destination.x - WIN_X (cw),
1450 			gw->destination.y - WIN_Y (cw),
1451 			FALSE, TRUE);
1452 
1453 	/* center the window to the main window */
1454 	gw->destination.x = WIN_CENTER_X (main) - (WIN_WIDTH (cw) / 2);
1455 	gw->destination.y = WIN_CENTER_Y (main) - (WIN_HEIGHT (cw) / 2);
1456 
1457 	/* Distance from destination. */
1458 	gw->mainTabOffset.x = WIN_X (cw) - gw->destination.x;
1459 	gw->mainTabOffset.y = WIN_Y (cw) - gw->destination.y;
1460 
1461 	if (gw->tx || gw->ty)
1462 	{
1463 	    gw->tx -= (WIN_X (cw) - gw->orgPos.x);
1464 	    gw->ty -= (WIN_Y (cw) - gw->orgPos.y);
1465 	}
1466 
1467 	gw->orgPos.x = WIN_X (cw);
1468 	gw->orgPos.y = WIN_Y (cw);
1469 
1470 	gw->animateState = IS_ANIMATED;
1471 	gw->xVelocity = gw->yVelocity = 0.0f;
1472     }
1473 
1474     groupStartTabbingAnimation (group, TRUE);
1475 }
1476 
1477 /*
1478  * groupUntabGroup
1479  *
1480  */
1481 void
groupUntabGroup(GroupSelection * group)1482 groupUntabGroup (GroupSelection *group)
1483 {
1484     int             oldX, oldY;
1485     CompWindow      *prevTopTab;
1486     GroupTabBarSlot *slot;
1487 
1488     if (!HAS_TOP_WIN (group))
1489 	return;
1490 
1491     GROUP_SCREEN (group->screen);
1492 
1493     if (group->prevTopTab)
1494 	prevTopTab = PREV_TOP_TAB (group);
1495     else
1496     {
1497 	/* If prevTopTab isn't set, we have no choice but using topTab.
1498 	   It happens when there is still animation, which
1499 	   means the tab wasn't changed anyway. */
1500 	prevTopTab = TOP_TAB (group);
1501     }
1502 
1503     group->lastTopTab = TOP_TAB (group);
1504     group->topTab = NULL;
1505 
1506     for (slot = group->tabBar->slots; slot; slot = slot->next)
1507     {
1508 	CompWindow *cw = slot->window;
1509 
1510 	GROUP_WINDOW (cw);
1511 
1512 	if (gw->animateState & (IS_ANIMATED | FINISHED_ANIMATION))
1513 	{
1514 	    gs->queued = TRUE;
1515 	    moveWindow (cw,
1516 			gw->destination.x - WIN_X (cw),
1517 			gw->destination.y - WIN_Y (cw),
1518 			FALSE, TRUE);
1519 	    gs->queued = FALSE;
1520 	}
1521 	groupSetWindowVisibility (cw, TRUE);
1522 
1523 	/* save the old original position - we might need it
1524 	   if constraining fails */
1525 	oldX = gw->orgPos.x;
1526 	oldY = gw->orgPos.y;
1527 
1528 	gw->orgPos.x = WIN_CENTER_X (prevTopTab) - WIN_WIDTH (cw) / 2;
1529 	gw->orgPos.y = WIN_CENTER_Y (prevTopTab) - WIN_HEIGHT (cw) / 2;
1530 
1531 	gw->destination.x = gw->orgPos.x + gw->mainTabOffset.x;
1532 	gw->destination.y = gw->orgPos.y + gw->mainTabOffset.y;
1533 
1534 	if (gw->tx || gw->ty)
1535 	{
1536 	    gw->tx -= (gw->orgPos.x - oldX);
1537 	    gw->ty -= (gw->orgPos.y - oldY);
1538 	}
1539 
1540 	gw->mainTabOffset.x = oldX;
1541 	gw->mainTabOffset.y = oldY;
1542 
1543 	gw->animateState = IS_ANIMATED;
1544 	gw->xVelocity = gw->yVelocity = 0.0f;
1545     }
1546 
1547     group->tabbingState = NoTabbing;
1548     groupStartTabbingAnimation (group, FALSE);
1549 
1550     groupDeleteTabBar (group);
1551     group->changeAnimationTime = 0;
1552     group->changeState = NoTabChange;
1553     group->nextTopTab = NULL;
1554     group->prevTopTab = NULL;
1555 
1556     damageScreen (group->screen);
1557 }
1558 
1559 /*
1560  * groupChangeTab
1561  *
1562  */
1563 Bool
groupChangeTab(GroupTabBarSlot * topTab,ChangeTabAnimationDirection direction)1564 groupChangeTab (GroupTabBarSlot             *topTab,
1565 		ChangeTabAnimationDirection direction)
1566 {
1567     CompWindow     *w, *oldTopTab;
1568     GroupSelection *group;
1569     CompScreen     *s;
1570 
1571     if (!topTab)
1572 	return TRUE;
1573 
1574     w = topTab->window;
1575     s = w->screen;
1576 
1577     GROUP_WINDOW (w);
1578 
1579     group = gw->group;
1580 
1581     if (!group || group->tabbingState != NoTabbing)
1582 	return TRUE;
1583 
1584     if (group->changeState == NoTabChange && group->topTab == topTab)
1585 	return TRUE;
1586 
1587     if (group->changeState != NoTabChange && group->nextTopTab == topTab)
1588 	return TRUE;
1589 
1590     oldTopTab = group->topTab ? group->topTab->window : NULL;
1591 
1592     if (group->changeState != NoTabChange)
1593 	group->nextDirection = direction;
1594     else if (direction == RotateLeft)
1595 	group->changeAnimationDirection = 1;
1596     else if (direction == RotateRight)
1597 	group->changeAnimationDirection = -1;
1598     else
1599     {
1600 	int             distanceOld = 0, distanceNew = 0;
1601 	GroupTabBarSlot *slot;
1602 
1603 	if (group->topTab)
1604 	    for (slot = group->tabBar->slots; slot && (slot != group->topTab);
1605 		 slot = slot->next, distanceOld++);
1606 
1607 	for (slot = group->tabBar->slots; slot && (slot != topTab);
1608 	     slot = slot->next, distanceNew++);
1609 
1610 	if (distanceNew < distanceOld)
1611 	    group->changeAnimationDirection = 1;   /*left */
1612 	else
1613 	    group->changeAnimationDirection = -1;  /* right */
1614 
1615 	/* check if the opposite direction is shorter */
1616 	if (abs (distanceNew - distanceOld) > (group->tabBar->nSlots / 2))
1617 	    group->changeAnimationDirection *= -1;
1618     }
1619 
1620     if (group->changeState != NoTabChange)
1621     {
1622 	if (group->prevTopTab == topTab)
1623 	{
1624 	    /* Reverse animation. */
1625 	    GroupTabBarSlot *tmp = group->topTab;
1626 	    group->topTab = group->prevTopTab;
1627 	    group->prevTopTab = tmp;
1628 
1629 	    group->changeAnimationDirection *= -1;
1630 	    group->changeAnimationTime =
1631 		groupGetChangeAnimationTime (s) * 500 -
1632 		group->changeAnimationTime;
1633 	    group->changeState = (group->changeState == TabChangeOldOut) ?
1634 		TabChangeNewIn : TabChangeOldOut;
1635 
1636 	    group->nextTopTab = NULL;
1637 	}
1638 	else
1639 	    group->nextTopTab = topTab;
1640     }
1641     else
1642     {
1643 	group->topTab = topTab;
1644 
1645 	groupRenderWindowTitle (group);
1646 	groupRenderTopTabHighlight (group);
1647 	if (oldTopTab)
1648 	    addWindowDamage (oldTopTab);
1649 	addWindowDamage (w);
1650     }
1651 
1652     if (topTab != group->nextTopTab)
1653     {
1654 	groupSetWindowVisibility (w, TRUE);
1655 	if (oldTopTab)
1656 	{
1657 	    int dx, dy;
1658 
1659 	    GROUP_SCREEN (s);
1660 
1661 	    dx = WIN_CENTER_X (oldTopTab) - WIN_CENTER_X (w);
1662 	    dy = WIN_CENTER_Y (oldTopTab) - WIN_CENTER_Y (w);
1663 
1664 	    gs->queued = TRUE;
1665 	    moveWindow (w, dx, dy, FALSE, TRUE);
1666 	    syncWindowPosition (w);
1667 	    gs->queued = FALSE;
1668 	}
1669 
1670 	if (HAS_PREV_TOP_WIN (group))
1671 	{
1672 	    /* we use only the half time here -
1673 	       the second half will be PaintFadeOut */
1674 	    group->changeAnimationTime =
1675 		groupGetChangeAnimationTime (s) * 500;
1676 	    groupTabChangeActivateEvent (s, TRUE);
1677 	    group->changeState = TabChangeOldOut;
1678 	}
1679 	else
1680 	{
1681 	    Bool activate;
1682 
1683 	    /* No window to do animation with. */
1684 	    if (HAS_TOP_WIN (group))
1685 		group->prevTopTab = group->topTab;
1686 	    else
1687 		group->prevTopTab = NULL;
1688 
1689 	    activate = !group->checkFocusAfterTabChange;
1690 	    if (!activate)
1691 	    {
1692 		CompFocusResult focus;
1693 
1694 		focus    = allowWindowFocus (w, NO_FOCUS_MASK, s->x, s->y, 0);
1695 		activate = focus == CompFocusAllowed;
1696 	    }
1697 
1698 	    if (activate)
1699 		(*s->activateWindow) (w);
1700 
1701 	    group->checkFocusAfterTabChange = FALSE;
1702 	}
1703     }
1704 
1705     return TRUE;
1706 }
1707 
1708 /*
1709  * groupRecalcSlotPos
1710  *
1711  */
1712 static void
groupRecalcSlotPos(GroupTabBarSlot * slot,int slotPos)1713 groupRecalcSlotPos (GroupTabBarSlot *slot,
1714 		    int             slotPos)
1715 {
1716     GroupSelection *group;
1717     XRectangle     box;
1718     int            space, thumbSize;
1719 
1720     GROUP_WINDOW (slot->window);
1721     group = gw->group;
1722 
1723     if (!HAS_TOP_WIN (group) || !group->tabBar)
1724 	return;
1725 
1726     space = groupGetThumbSpace (slot->window->screen);
1727     thumbSize = groupGetThumbSize (slot->window->screen);
1728 
1729     EMPTY_REGION (slot->region);
1730 
1731     box.x = space + ((thumbSize + space) * slotPos);
1732     box.y = space;
1733 
1734     box.width = thumbSize;
1735     box.height = thumbSize;
1736 
1737     XUnionRectWithRegion (&box, slot->region, slot->region);
1738 }
1739 
1740 /*
1741  * groupRecalcTabBarPos
1742  *
1743  */
1744 void
groupRecalcTabBarPos(GroupSelection * group,int middleX,int minX1,int maxX2)1745 groupRecalcTabBarPos (GroupSelection *group,
1746 		      int            middleX,
1747 		      int            minX1,
1748 		      int            maxX2)
1749 {
1750     GroupTabBarSlot *slot;
1751     GroupTabBar     *bar;
1752     CompWindow      *topTab;
1753     Bool            isDraggedSlotGroup = FALSE;
1754     int             space, barWidth;
1755     int             thumbSize;
1756     int             tabsWidth = 0, tabsHeight = 0;
1757     int             currentSlot;
1758     XRectangle      box;
1759 
1760     if (!HAS_TOP_WIN (group) || !group->tabBar)
1761 	return;
1762 
1763     GROUP_SCREEN (group->screen);
1764 
1765     bar = group->tabBar;
1766     topTab = TOP_TAB (group);
1767     space = groupGetThumbSpace (group->screen);
1768 
1769     /* calculate the space which the tabs need */
1770     for (slot = bar->slots; slot; slot = slot->next)
1771     {
1772 	if (slot == gs->draggedSlot && gs->dragged)
1773 	{
1774 	    isDraggedSlotGroup = TRUE;
1775 	    continue;
1776 	}
1777 
1778 	tabsWidth += (slot->region->extents.x2 - slot->region->extents.x1);
1779 	if ((slot->region->extents.y2 - slot->region->extents.y1) > tabsHeight)
1780 	    tabsHeight = slot->region->extents.y2 - slot->region->extents.y1;
1781     }
1782 
1783     /* just a little work-a-round for first call
1784        FIXME: remove this! */
1785     thumbSize = groupGetThumbSize (group->screen);
1786     if (bar->nSlots && tabsWidth <= 0)
1787     {
1788 	/* first call */
1789 	tabsWidth = thumbSize * bar->nSlots;
1790 
1791 	if (bar->nSlots && tabsHeight < thumbSize)
1792 	{
1793 	    /* we need to do the standard height too */
1794 	    tabsHeight = thumbSize;
1795 	}
1796 
1797 	if (isDraggedSlotGroup)
1798 	    tabsWidth -= thumbSize;
1799     }
1800 
1801     barWidth = space * (bar->nSlots + 1) + tabsWidth;
1802 
1803     if (isDraggedSlotGroup)
1804     {
1805 	/* 1 tab is missing, so we have 1 less border */
1806 	barWidth -= space;
1807     }
1808 
1809     if (maxX2 - minX1 < barWidth)
1810 	box.x = (maxX2 + minX1) / 2 - barWidth / 2;
1811     else if (middleX - barWidth / 2 < minX1)
1812 	box.x = minX1;
1813     else if (middleX + barWidth / 2 > maxX2)
1814 	box.x = maxX2 - barWidth;
1815     else
1816 	box.x = middleX - barWidth / 2;
1817 
1818     box.y = WIN_Y (topTab);
1819     box.width = barWidth;
1820     box.height = space * 2 + tabsHeight;
1821 
1822     groupResizeTabBarRegion (group, &box, TRUE);
1823 
1824     /* recalc every slot region */
1825     currentSlot = 0;
1826     for (slot = bar->slots; slot; slot = slot->next)
1827     {
1828 	if (slot == gs->draggedSlot && gs->dragged)
1829 	    continue;
1830 
1831 	groupRecalcSlotPos (slot, currentSlot);
1832 	XOffsetRegion (slot->region,
1833 		       bar->region->extents.x1,
1834 		       bar->region->extents.y1);
1835 
1836 	slot->springX = (slot->region->extents.x1 +
1837 			 slot->region->extents.x2) / 2;
1838 	slot->speed = 0;
1839 	slot->msSinceLastMove = 0;
1840 
1841 	currentSlot++;
1842     }
1843 
1844     bar->leftSpringX = box.x;
1845     bar->rightSpringX = box.x + box.width;
1846 
1847     bar->rightSpeed = 0;
1848     bar->leftSpeed = 0;
1849 
1850     bar->rightMsSinceLastMove = 0;
1851     bar->leftMsSinceLastMove = 0;
1852 }
1853 
1854 void
groupDamageTabBarRegion(GroupSelection * group)1855 groupDamageTabBarRegion (GroupSelection *group)
1856 {
1857     REGION reg;
1858 
1859     reg.rects = &reg.extents;
1860     reg.numRects = 1;
1861 
1862     /* we use 15 pixels as damage buffer here, as there is a 10 pixel wide
1863        border around the selected slot which also needs to be damaged
1864        properly - however the best way would be if slot->region was
1865        sized including the border */
1866 
1867 #define DAMAGE_BUFFER 20
1868 
1869     reg.extents = group->tabBar->region->extents;
1870 
1871     if (group->tabBar->slots)
1872     {
1873 	reg.extents.x1 = MIN (reg.extents.x1,
1874 			      group->tabBar->slots->region->extents.x1);
1875 	reg.extents.y1 = MIN (reg.extents.y1,
1876 			      group->tabBar->slots->region->extents.y1);
1877 	reg.extents.x2 = MAX (reg.extents.x2,
1878 			      group->tabBar->revSlots->region->extents.x2);
1879 	reg.extents.y2 = MAX (reg.extents.y2,
1880 			      group->tabBar->revSlots->region->extents.y2);
1881     }
1882 
1883     reg.extents.x1 -= DAMAGE_BUFFER;
1884     reg.extents.y1 -= DAMAGE_BUFFER;
1885     reg.extents.x2 += DAMAGE_BUFFER;
1886     reg.extents.y2 += DAMAGE_BUFFER;
1887 
1888     damageScreenRegion (group->screen, &reg);
1889 }
1890 
1891 void
groupMoveTabBarRegion(GroupSelection * group,int dx,int dy,Bool syncIPW)1892 groupMoveTabBarRegion (GroupSelection *group,
1893 		       int            dx,
1894 		       int            dy,
1895 		       Bool           syncIPW)
1896 {
1897     groupDamageTabBarRegion (group);
1898 
1899     XOffsetRegion (group->tabBar->region, dx, dy);
1900 
1901     if (syncIPW)
1902 	XMoveWindow (group->screen->display->display,
1903 		     group->inputPrevention,
1904 		     group->tabBar->leftSpringX,
1905 		     group->tabBar->region->extents.y1);
1906 
1907     groupDamageTabBarRegion (group);
1908 }
1909 
1910 void
groupResizeTabBarRegion(GroupSelection * group,XRectangle * box,Bool syncIPW)1911 groupResizeTabBarRegion (GroupSelection *group,
1912 			 XRectangle     *box,
1913 			 Bool           syncIPW)
1914 {
1915     int oldWidth;
1916 
1917     groupDamageTabBarRegion (group);
1918 
1919     oldWidth = group->tabBar->region->extents.x2 -
1920 	group->tabBar->region->extents.x1;
1921 
1922     if (group->tabBar->bgLayer && oldWidth != box->width && syncIPW)
1923     {
1924 	group->tabBar->bgLayer =
1925 	    groupRebuildCairoLayer (group->screen,
1926 				    group->tabBar->bgLayer,
1927 				    box->width +
1928 				    groupGetThumbSpace (group->screen) +
1929 				    groupGetThumbSize (group->screen),
1930 				    box->height);
1931 	groupRenderTabBarBackground (group);
1932 
1933 	/* invalidate old width */
1934 	group->tabBar->oldWidth = 0;
1935     }
1936 
1937     EMPTY_REGION (group->tabBar->region);
1938     XUnionRectWithRegion (box, group->tabBar->region, group->tabBar->region);
1939 
1940     if (syncIPW)
1941     {
1942 	XWindowChanges xwc;
1943 
1944 	xwc.x = box->x;
1945 	xwc.y = box->y;
1946 	xwc.width = box->width;
1947 	xwc.height = box->height;
1948 
1949 	xwc.stack_mode = Above;
1950 	xwc.sibling = HAS_TOP_WIN (group) ? TOP_TAB (group)->id : None;
1951 
1952 	XConfigureWindow (group->screen->display->display,
1953 			  group->inputPrevention,
1954 			  CWSibling | CWStackMode | CWX | CWY |
1955 			  CWWidth | CWHeight,
1956 			  &xwc);
1957     }
1958 
1959     groupDamageTabBarRegion (group);
1960 }
1961 
1962 /*
1963  * groupInsertTabBarSlotBefore
1964  *
1965  */
1966 void
groupInsertTabBarSlotBefore(GroupTabBar * bar,GroupTabBarSlot * slot,GroupTabBarSlot * nextSlot)1967 groupInsertTabBarSlotBefore (GroupTabBar     *bar,
1968 			     GroupTabBarSlot *slot,
1969 			     GroupTabBarSlot *nextSlot)
1970 {
1971     GroupTabBarSlot *prev = nextSlot->prev;
1972     CompWindow      *w = slot->window;
1973 
1974     GROUP_WINDOW (w);
1975 
1976     if (prev)
1977     {
1978 	slot->prev = prev;
1979 	prev->next = slot;
1980     }
1981     else
1982     {
1983 	bar->slots = slot;
1984 	slot->prev = NULL;
1985     }
1986 
1987     slot->next = nextSlot;
1988     nextSlot->prev = slot;
1989     bar->nSlots++;
1990 
1991     /* Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
1992        because the tab-bar got wider now, so it will put it in
1993        the average between them, which is
1994        (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway. */
1995     groupRecalcTabBarPos (gw->group,
1996 			  (bar->region->extents.x1 +
1997 			   bar->region->extents.x2) / 2,
1998 			  bar->region->extents.x1, bar->region->extents.x2);
1999 }
2000 
2001 /*
2002  * groupInsertTabBarSlotAfter
2003  *
2004  */
2005 void
groupInsertTabBarSlotAfter(GroupTabBar * bar,GroupTabBarSlot * slot,GroupTabBarSlot * prevSlot)2006 groupInsertTabBarSlotAfter (GroupTabBar     *bar,
2007 			    GroupTabBarSlot *slot,
2008 			    GroupTabBarSlot *prevSlot)
2009 {
2010     GroupTabBarSlot *next = prevSlot->next;
2011     CompWindow      *w = slot->window;
2012 
2013     GROUP_WINDOW (w);
2014 
2015     if (next)
2016     {
2017 	slot->next = next;
2018 	next->prev = slot;
2019     }
2020     else
2021     {
2022 	bar->revSlots = slot;
2023 	slot->next = NULL;
2024     }
2025 
2026     slot->prev = prevSlot;
2027     prevSlot->next = slot;
2028     bar->nSlots++;
2029 
2030     /* Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
2031        because the tab-bar got wider now, so it will put it in the
2032        average between them, which is
2033        (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway. */
2034     groupRecalcTabBarPos (gw->group,
2035 			  (bar->region->extents.x1 +
2036 			   bar->region->extents.x2) / 2,
2037 			  bar->region->extents.x1, bar->region->extents.x2);
2038 }
2039 
2040 /*
2041  * groupInsertTabBarSlot
2042  *
2043  */
2044 void
groupInsertTabBarSlot(GroupTabBar * bar,GroupTabBarSlot * slot)2045 groupInsertTabBarSlot (GroupTabBar     *bar,
2046 		       GroupTabBarSlot *slot)
2047 {
2048     CompWindow *w = slot->window;
2049 
2050     GROUP_WINDOW (w);
2051 
2052     if (bar->slots)
2053     {
2054 	bar->revSlots->next = slot;
2055 	slot->prev = bar->revSlots;
2056 	slot->next = NULL;
2057     }
2058     else
2059     {
2060 	slot->prev = NULL;
2061 	slot->next = NULL;
2062 	bar->slots = slot;
2063     }
2064 
2065     bar->revSlots = slot;
2066     bar->nSlots++;
2067 
2068     /* Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
2069        because the tab-bar got wider now, so it will put it in
2070        the average between them, which is
2071        (bar->region->extents.x1 + bar->region->extents.x2) / 2 anyway. */
2072     groupRecalcTabBarPos (gw->group,
2073 			  (bar->region->extents.x1 +
2074 			   bar->region->extents.x2) / 2,
2075 			  bar->region->extents.x1, bar->region->extents.x2);
2076 }
2077 
2078 /*
2079  * groupUnhookTabBarSlot
2080  *
2081  */
2082 void
groupUnhookTabBarSlot(GroupTabBar * bar,GroupTabBarSlot * slot,Bool temporary)2083 groupUnhookTabBarSlot (GroupTabBar     *bar,
2084 		       GroupTabBarSlot *slot,
2085 		       Bool            temporary)
2086 {
2087     GroupTabBarSlot *tempSlot;
2088     GroupTabBarSlot *prev = slot->prev;
2089     GroupTabBarSlot *next = slot->next;
2090     CompWindow      *w = slot->window;
2091     CompScreen      *s = w->screen;
2092     GroupSelection  *group;
2093 
2094     GROUP_WINDOW (w);
2095 
2096     group = gw->group;
2097 
2098     /* check if slot is not already unhooked */
2099     for (tempSlot = bar->slots; tempSlot; tempSlot = tempSlot->next)
2100 	if (tempSlot == slot)
2101 	    break;
2102 
2103     if (!tempSlot)
2104 	return;
2105 
2106     if (prev)
2107 	prev->next = next;
2108     else
2109 	bar->slots = next;
2110 
2111     if (next)
2112 	next->prev = prev;
2113     else
2114 	bar->revSlots = prev;
2115 
2116     slot->prev = NULL;
2117     slot->next = NULL;
2118     bar->nSlots--;
2119 
2120     if (!temporary)
2121     {
2122 	if (IS_PREV_TOP_TAB (w, group))
2123 	    group->prevTopTab = NULL;
2124 	if (IS_TOP_TAB (w, group))
2125 	{
2126 	    group->topTab = NULL;
2127 
2128 	    if (next)
2129 		groupChangeTab (next, RotateRight);
2130 	    else if (prev)
2131 		groupChangeTab (prev, RotateLeft);
2132 
2133 	    if (groupGetUntabOnClose (s))
2134 		groupUntabGroup (group);
2135 	}
2136     }
2137 
2138     if (slot == bar->hoveredSlot)
2139 	bar->hoveredSlot = NULL;
2140 
2141     if (slot == bar->textSlot)
2142     {
2143 	bar->textSlot = NULL;
2144 
2145 	if (bar->textLayer)
2146 	{
2147 	    if (bar->textLayer->state == PaintFadeIn ||
2148 		bar->textLayer->state == PaintOn)
2149 	    {
2150 		bar->textLayer->animationTime =
2151 		    (groupGetFadeTextTime (s) * 1000) -
2152 		    bar->textLayer->animationTime;
2153 		bar->textLayer->state = PaintFadeOut;
2154 	    }
2155 	}
2156     }
2157 
2158     /* Moving bar->region->extents.x1 / x2 as minX1 / maxX2 will work,
2159        because the tab-bar got thiner now, so
2160        (bar->region->extents.x1 + bar->region->extents.x2) / 2
2161        Won't cause the new x1 / x2 to be outside the original region. */
2162     groupRecalcTabBarPos (group,
2163 			  (bar->region->extents.x1 +
2164 			   bar->region->extents.x2) / 2,
2165 			  bar->region->extents.x1,
2166 			  bar->region->extents.x2);
2167 }
2168 
2169 /*
2170  * groupDeleteTabBarSlot
2171  *
2172  */
2173 void
groupDeleteTabBarSlot(GroupTabBar * bar,GroupTabBarSlot * slot)2174 groupDeleteTabBarSlot (GroupTabBar     *bar,
2175 		       GroupTabBarSlot *slot)
2176 {
2177     CompWindow *w = slot->window;
2178 
2179     GROUP_WINDOW (w);
2180     GROUP_SCREEN (w->screen);
2181 
2182     groupUnhookTabBarSlot (bar, slot, FALSE);
2183 
2184     if (slot->region)
2185 	XDestroyRegion (slot->region);
2186 
2187     if (slot == gs->draggedSlot)
2188     {
2189 	gs->draggedSlot = NULL;
2190 	gs->dragged = FALSE;
2191 
2192 	if (gs->grabState == ScreenGrabTabDrag)
2193 	    groupGrabScreen (w->screen, ScreenGrabNone);
2194     }
2195 
2196     gw->slot = NULL;
2197     groupUpdateWindowProperty (w);
2198     free (slot);
2199 }
2200 
2201 /*
2202  * groupCreateSlot
2203  *
2204  */
groupCreateSlot(GroupSelection * group,CompWindow * w)2205 void groupCreateSlot (GroupSelection *group,
2206 		      CompWindow     *w)
2207 {
2208     GroupTabBarSlot *slot;
2209 
2210     GROUP_WINDOW (w);
2211 
2212     if (!group->tabBar)
2213 	return;
2214 
2215     slot = malloc (sizeof (GroupTabBarSlot));
2216     if (!slot)
2217         return;
2218 
2219     slot->window = w;
2220 
2221     slot->region = XCreateRegion ();
2222 
2223     groupInsertTabBarSlot (group->tabBar, slot);
2224     gw->slot = slot;
2225     groupUpdateWindowProperty (w);
2226 }
2227 
2228 #define SPRING_K     groupGetDragSpringK(s)
2229 #define FRICTION     groupGetDragFriction(s)
2230 #define SIZE	     groupGetThumbSize(s)
2231 #define BORDER	     groupGetBorderRadius(s)
2232 #define Y_START_MOVE groupGetDragYDistance(s)
2233 #define SPEED_LIMIT  groupGetDragSpeedLimit(s)
2234 
2235 /*
2236  * groupSpringForce
2237  *
2238  */
2239 static inline int
groupSpringForce(CompScreen * s,int centerX,int springX)2240 groupSpringForce (CompScreen *s,
2241 		  int        centerX,
2242 		  int        springX)
2243 {
2244     /* Each slot has a spring attached to it, starting at springX,
2245        and ending at the center of the slot (centerX).
2246        The spring will cause the slot to move, using the
2247        well-known physical formula F = k * dl... */
2248     return -SPRING_K * (centerX - springX);
2249 }
2250 
2251 /*
2252  * groupDraggedSlotForce
2253  *
2254  */
2255 static int
groupDraggedSlotForce(CompScreen * s,int distanceX,int distanceY)2256 groupDraggedSlotForce (CompScreen *s,
2257 		       int        distanceX,
2258 		       int        distanceY)
2259 {
2260     /* The dragged slot will make the slot move, to get
2261        DnD animations (slots will make room for the newly inserted slot).
2262        As the dragged slot is closer to the slot, it will put
2263        more force on the slot, causing it to make room for the dragged slot...
2264        But if the dragged slot gets too close to the slot, they are
2265        going to be reordered soon, so the force will get lower.
2266 
2267        If the dragged slot is in the other side of the slot,
2268        it will have to make force in the opposite direction.
2269 
2270        So we the needed funtion is an odd function that goes
2271        up at first, and down after that.
2272        Sinus is a function like that... :)
2273 
2274        The maximum is got when x = (x1 + x2) / 2,
2275        in this case: x = SIZE + BORDER.
2276        Because of that, for x = SIZE + BORDER,
2277        we get a force of SPRING_K * (SIZE + BORDER) / 2.
2278        That equals to the force we get from the the spring.
2279        This way, the slot won't move when its distance from
2280        the dragged slot is SIZE + BORDER (which is the default
2281        distance between slots).
2282        */
2283 
2284     /* The maximum value */
2285     float a = SPRING_K * (SIZE + BORDER) / 2;
2286     /* This will make distanceX == 2 * (SIZE + BORDER) to get 0,
2287        and distanceX == (SIZE + BORDER) to get the maximum. */
2288     float b = PI /  (2 * SIZE + 2 * BORDER);
2289 
2290     /* If there is some distance between the slots in the y axis,
2291        the slot should get less force... For this, we change max
2292        to a lower value, using a simple linear function. */
2293 
2294     if (distanceY < Y_START_MOVE)
2295 	a *= 1.0f - (float)distanceY / Y_START_MOVE;
2296     else
2297 	a = 0;
2298 
2299     if (abs (distanceX) < 2 * (SIZE + BORDER))
2300 	return a * sin (b * distanceX);
2301     else
2302 	return 0;
2303 }
2304 
2305 /*
2306  * groupApplyFriction
2307  *
2308  */
2309 static inline void
groupApplyFriction(CompScreen * s,int * speed)2310 groupApplyFriction (CompScreen *s,
2311 		    int        *speed)
2312 {
2313     if (abs (*speed) < FRICTION)
2314 	*speed = 0;
2315     else if (*speed > 0)
2316 	*speed -= FRICTION;
2317     else if (*speed < 0)
2318 	*speed += FRICTION;
2319 }
2320 
2321 /*
2322  * groupApplySpeedLimit
2323  *
2324  */
2325 static inline void
groupApplySpeedLimit(CompScreen * s,int * speed)2326 groupApplySpeedLimit (CompScreen *s,
2327 		      int        *speed)
2328 {
2329     if (*speed > SPEED_LIMIT)
2330 	*speed = SPEED_LIMIT;
2331     else if (*speed < -SPEED_LIMIT)
2332 	*speed = -SPEED_LIMIT;
2333 }
2334 
2335 /*
2336  * groupApplyForces
2337  *
2338  */
2339 void
groupApplyForces(CompScreen * s,GroupTabBar * bar,GroupTabBarSlot * draggedSlot)2340 groupApplyForces (CompScreen      *s,
2341 		  GroupTabBar     *bar,
2342 		  GroupTabBarSlot *draggedSlot)
2343 {
2344     GroupTabBarSlot *slot, *slot2;
2345     int             centerX, centerY;
2346     int             draggedCenterX, draggedCenterY;
2347 
2348     if (draggedSlot)
2349     {
2350 	int vx, vy;
2351 
2352 	groupGetDrawOffsetForSlot (draggedSlot, &vx, &vy);
2353 
2354 	draggedCenterX = ((draggedSlot->region->extents.x1 +
2355 			   draggedSlot->region->extents.x2) / 2) + vx;
2356 	draggedCenterY = ((draggedSlot->region->extents.y1 +
2357 			   draggedSlot->region->extents.y2) / 2) + vy;
2358     }
2359     else
2360     {
2361 	draggedCenterX = 0;
2362 	draggedCenterY = 0;
2363     }
2364 
2365     bar->leftSpeed += groupSpringForce(s,
2366 				       bar->region->extents.x1,
2367 				       bar->leftSpringX);
2368     bar->rightSpeed += groupSpringForce(s,
2369 					bar->region->extents.x2,
2370 					bar->rightSpringX);
2371 
2372     if (draggedSlot)
2373     {
2374 	int leftForce, rightForce;
2375 
2376 	leftForce = groupDraggedSlotForce(s,
2377 					  bar->region->extents.x1 -
2378 					  SIZE / 2 - draggedCenterX,
2379 					  abs ((bar->region->extents.y1 +
2380 						bar->region->extents.y2) / 2 -
2381 					       draggedCenterY));
2382 
2383 	rightForce = groupDraggedSlotForce (s,
2384 					    bar->region->extents.x2 +
2385 					    SIZE / 2 - draggedCenterX,
2386 					    abs ((bar->region->extents.y1 +
2387 						  bar->region->extents.y2) / 2 -
2388 						 draggedCenterY));
2389 
2390 	if (leftForce < 0)
2391 	    bar->leftSpeed += leftForce;
2392 	if (rightForce > 0)
2393 	    bar->rightSpeed += rightForce;
2394     }
2395 
2396     for (slot = bar->slots; slot; slot = slot->next)
2397     {
2398 	centerX = (slot->region->extents.x1 + slot->region->extents.x2) / 2;
2399 	centerY = (slot->region->extents.y1 + slot->region->extents.y2) / 2;
2400 
2401 	slot->speed += groupSpringForce (s, centerX, slot->springX);
2402 
2403 	if (draggedSlot && draggedSlot != slot)
2404 	{
2405 	    int draggedSlotForce;
2406 	    draggedSlotForce =
2407 		groupDraggedSlotForce(s, centerX - draggedCenterX,
2408 				      abs (centerY - draggedCenterY));
2409 
2410 	    slot->speed += draggedSlotForce;
2411 	    slot2 = NULL;
2412 
2413 	    if (draggedSlotForce < 0)
2414 	    {
2415 		slot2 = slot->prev;
2416 		bar->leftSpeed += draggedSlotForce;
2417 	    }
2418 	    else if (draggedSlotForce > 0)
2419 	    {
2420 		slot2 = slot->next;
2421 		bar->rightSpeed += draggedSlotForce;
2422 	    }
2423 
2424 	    while (slot2)
2425 	    {
2426 		if (slot2 != draggedSlot)
2427 		    slot2->speed += draggedSlotForce;
2428 
2429 		slot2 = (draggedSlotForce < 0) ? slot2->prev : slot2->next;
2430 	    }
2431 	}
2432     }
2433 
2434     for (slot = bar->slots; slot; slot = slot->next)
2435     {
2436 	groupApplyFriction (s, &slot->speed);
2437 	groupApplySpeedLimit (s, &slot->speed);
2438     }
2439 
2440     groupApplyFriction (s, &bar->leftSpeed);
2441     groupApplySpeedLimit (s, &bar->leftSpeed);
2442 
2443     groupApplyFriction (s, &bar->rightSpeed);
2444     groupApplySpeedLimit (s, &bar->rightSpeed);
2445 }
2446 
2447 /*
2448  * groupApplySpeeds
2449  *
2450  */
2451 void
groupApplySpeeds(CompScreen * s,GroupSelection * group,int msSinceLastRepaint)2452 groupApplySpeeds (CompScreen     *s,
2453 		  GroupSelection *group,
2454 		  int            msSinceLastRepaint)
2455 {
2456     GroupTabBar     *bar = group->tabBar;
2457     GroupTabBarSlot *slot;
2458     int             move;
2459     XRectangle      box;
2460     Bool            updateTabBar = FALSE;
2461 
2462     box.x = bar->region->extents.x1;
2463     box.y = bar->region->extents.y1;
2464     box.width = bar->region->extents.x2 - bar->region->extents.x1;
2465     box.height = bar->region->extents.y2 - bar->region->extents.y1;
2466 
2467     bar->leftMsSinceLastMove += msSinceLastRepaint;
2468     bar->rightMsSinceLastMove += msSinceLastRepaint;
2469 
2470     /* Left */
2471     move = bar->leftSpeed * bar->leftMsSinceLastMove / 1000;
2472     if (move)
2473     {
2474 	box.x += move;
2475 	box.width -= move;
2476 
2477 	bar->leftMsSinceLastMove = 0;
2478 	updateTabBar = TRUE;
2479     }
2480     else if (bar->leftSpeed == 0 &&
2481 	     bar->region->extents.x1 != bar->leftSpringX &&
2482 	     (SPRING_K * abs (bar->region->extents.x1 - bar->leftSpringX) <
2483 	      FRICTION))
2484     {
2485 	/* Friction is preventing from the left border to get
2486 	   to its original position. */
2487 	box.x += bar->leftSpringX - bar->region->extents.x1;
2488 	box.width -= bar->leftSpringX - bar->region->extents.x1;
2489 
2490 	bar->leftMsSinceLastMove = 0;
2491 	updateTabBar = TRUE;
2492     }
2493     else if (bar->leftSpeed == 0)
2494 	bar->leftMsSinceLastMove = 0;
2495 
2496     /* Right */
2497     move = bar->rightSpeed * bar->rightMsSinceLastMove / 1000;
2498     if (move)
2499     {
2500 	box.width += move;
2501 
2502 	bar->rightMsSinceLastMove = 0;
2503 	updateTabBar = TRUE;
2504     }
2505     else if (bar->rightSpeed == 0 &&
2506 	     bar->region->extents.x2 != bar->rightSpringX &&
2507 	     (SPRING_K * abs (bar->region->extents.x2 - bar->rightSpringX) <
2508 	      FRICTION))
2509     {
2510 	/* Friction is preventing from the right border to get
2511 	   to its original position. */
2512 	box.width += bar->leftSpringX - bar->region->extents.x1;
2513 
2514 	bar->leftMsSinceLastMove = 0;
2515 	updateTabBar = TRUE;
2516     }
2517     else if (bar->rightSpeed == 0)
2518 	bar->rightMsSinceLastMove = 0;
2519 
2520     if (updateTabBar)
2521 	groupResizeTabBarRegion (group, &box, FALSE);
2522 
2523     for (slot = bar->slots; slot; slot = slot->next)
2524     {
2525 	int slotCenter;
2526 
2527 	slot->msSinceLastMove += msSinceLastRepaint;
2528 	move = slot->speed * slot->msSinceLastMove / 1000;
2529 	slotCenter = (slot->region->extents.x1 +
2530 		      slot->region->extents.x2) / 2;
2531 
2532 	if (move)
2533 	{
2534 	    XOffsetRegion (slot->region, move, 0);
2535 	    slot->msSinceLastMove = 0;
2536 	}
2537 	else if (slot->speed == 0 &&
2538 		 slotCenter != slot->springX &&
2539 		 SPRING_K * abs (slotCenter - slot->springX) < FRICTION)
2540 	{
2541 	    /* Friction is preventing from the slot to get
2542 	       to its original position. */
2543 
2544 	    XOffsetRegion (slot->region, slot->springX - slotCenter, 0);
2545 	    slot->msSinceLastMove = 0;
2546 	}
2547 	else if (slot->speed == 0)
2548 	    slot->msSinceLastMove = 0;
2549     }
2550 }
2551 
2552 /*
2553  * groupInitTabBar
2554  *
2555  */
2556 void
groupInitTabBar(GroupSelection * group,CompWindow * topTab)2557 groupInitTabBar (GroupSelection *group,
2558 		 CompWindow     *topTab)
2559 {
2560     GroupTabBar *bar;
2561     int         i;
2562 
2563     if (group->tabBar)
2564 	return;
2565 
2566     bar = malloc (sizeof (GroupTabBar));
2567     if (!bar)
2568 	return;
2569 
2570     bar->slots = NULL;
2571     bar->nSlots = 0;
2572     bar->bgAnimation = AnimationNone;
2573     bar->bgAnimationTime = 0;
2574     bar->state = PaintOff;
2575     bar->animationTime = 0;
2576     bar->timeoutHandle = 0;
2577     bar->textLayer = NULL;
2578     bar->bgLayer = NULL;
2579     bar->selectionLayer = NULL;
2580     bar->hoveredSlot = NULL;
2581     bar->textSlot = NULL;
2582     bar->oldWidth = 0;
2583     group->tabBar = bar;
2584 
2585     bar->region = XCreateRegion ();
2586 
2587     for (i = 0; i < group->nWins; i++)
2588 	groupCreateSlot (group, group->windows[i]);
2589 
2590     groupRecalcTabBarPos (group, WIN_CENTER_X (topTab),
2591 			  WIN_X (topTab), WIN_X (topTab) + WIN_WIDTH (topTab));
2592 }
2593 
2594 /*
2595  * groupDeleteTabBar
2596  *
2597  */
2598 void
groupDeleteTabBar(GroupSelection * group)2599 groupDeleteTabBar (GroupSelection *group)
2600 {
2601     GroupTabBar *bar = group->tabBar;
2602 
2603     groupDestroyCairoLayer (group->screen, bar->textLayer);
2604     groupDestroyCairoLayer (group->screen, bar->bgLayer);
2605     groupDestroyCairoLayer (group->screen, bar->selectionLayer);
2606 
2607     groupDestroyInputPreventionWindow (group);
2608 
2609     if (bar->timeoutHandle)
2610 	compRemoveTimeout (bar->timeoutHandle);
2611 
2612     while (bar->slots)
2613 	groupDeleteTabBarSlot (bar, bar->slots);
2614 
2615     if (bar->region)
2616 	XDestroyRegion (bar->region);
2617 
2618     free (bar);
2619     group->tabBar = NULL;
2620 }
2621 
2622 /*
2623  * groupInitTab
2624  *
2625  */
2626 Bool
groupInitTab(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)2627 groupInitTab (CompDisplay     *d,
2628 	      CompAction      *action,
2629 	      CompActionState state,
2630 	      CompOption      *option,
2631 	      int             nOption)
2632 {
2633     Window     xid;
2634     CompWindow *w;
2635     Bool       allowUntab = TRUE;
2636 
2637     xid = getIntOptionNamed (option, nOption, "window", 0);
2638     w   = findWindowAtDisplay (d, xid);
2639     if (!w)
2640 	return TRUE;
2641 
2642     GROUP_WINDOW (w);
2643 
2644     if (gw->inSelection)
2645     {
2646 	groupGroupWindows (d, action, state, option, nOption);
2647 	/* If the window was selected, we don't want to
2648 	   untab the group, because the user probably
2649 	   wanted to tab the selected windows. */
2650 	allowUntab = FALSE;
2651     }
2652 
2653     if (!gw->group)
2654 	return TRUE;
2655 
2656     if (!gw->group->tabBar)
2657 	groupTabGroup (w);
2658     else if (allowUntab)
2659 	groupUntabGroup (gw->group);
2660 
2661     damageScreen (w->screen);
2662 
2663     return TRUE;
2664 }
2665 
2666 /*
2667  * groupChangeTabLeft
2668  *
2669  */
2670 Bool
groupChangeTabLeft(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)2671 groupChangeTabLeft (CompDisplay     *d,
2672 		    CompAction      *action,
2673 		    CompActionState state,
2674 		    CompOption      *option,
2675 		    int             nOption)
2676 {
2677     Window     xid;
2678     CompWindow *w, *topTab;
2679 
2680     xid = getIntOptionNamed (option, nOption, "window", 0);
2681     w   = topTab = findWindowAtDisplay (d, xid);
2682     if (!w)
2683 	return TRUE;
2684 
2685     GROUP_WINDOW (w);
2686     GROUP_SCREEN (w->screen);
2687 
2688     if (!gw->slot || !gw->group)
2689 	return TRUE;
2690 
2691     if (gw->group->nextTopTab)
2692 	topTab = NEXT_TOP_TAB (gw->group);
2693     else if (gw->group->topTab)
2694     {
2695 	/* If there are no tabbing animations,
2696 	   topTab is never NULL. */
2697 	topTab = TOP_TAB (gw->group);
2698     }
2699 
2700     gw = GET_GROUP_WINDOW (topTab, gs);
2701 
2702     if (gw->slot->prev)
2703 	return groupChangeTab (gw->slot->prev, RotateLeft);
2704     else
2705 	return groupChangeTab (gw->group->tabBar->revSlots, RotateLeft);
2706 }
2707 
2708 /*
2709  * groupChangeTabRight
2710  *
2711  */
2712 Bool
groupChangeTabRight(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)2713 groupChangeTabRight (CompDisplay     *d,
2714 		     CompAction      *action,
2715 		     CompActionState state,
2716 		     CompOption      *option,
2717 		     int             nOption)
2718 {
2719     Window     xid;
2720     CompWindow *w, *topTab;
2721 
2722     xid = getIntOptionNamed (option, nOption, "window", 0);
2723     w   = topTab = findWindowAtDisplay (d, xid);
2724     if (!w)
2725 	return TRUE;
2726 
2727     GROUP_WINDOW (w);
2728     GROUP_SCREEN (w->screen);
2729 
2730     if (!gw->slot || !gw->group)
2731 	return TRUE;
2732 
2733     if (gw->group->nextTopTab)
2734 	topTab = NEXT_TOP_TAB (gw->group);
2735     else if (gw->group->topTab)
2736     {
2737 	/* If there are no tabbing animations,
2738 	   topTab is never NULL. */
2739 	topTab = TOP_TAB (gw->group);
2740     }
2741 
2742     gw = GET_GROUP_WINDOW (topTab, gs);
2743 
2744     if (gw->slot->next)
2745 	return groupChangeTab (gw->slot->next, RotateRight);
2746     else
2747 	return groupChangeTab (gw->group->tabBar->slots, RotateRight);
2748 }
2749 
2750 /*
2751  * groupSwitchTopTabInput
2752  *
2753  */
2754 void
groupSwitchTopTabInput(GroupSelection * group,Bool enable)2755 groupSwitchTopTabInput (GroupSelection *group,
2756 			Bool           enable)
2757 {
2758     if (!group->tabBar || !HAS_TOP_WIN (group))
2759 	return;
2760 
2761     if (!group->inputPrevention)
2762 	groupCreateInputPreventionWindow (group);
2763 
2764     if (!enable)
2765 	XMapWindow (group->screen->display->display,
2766 		    group->inputPrevention);
2767     else
2768 	XUnmapWindow (group->screen->display->display,
2769 		      group->inputPrevention);
2770 
2771     group->ipwMapped = !enable;
2772 }
2773 
2774 /*
2775  * groupCreateInputPreventionWindow
2776  *
2777  */
2778 void
groupCreateInputPreventionWindow(GroupSelection * group)2779 groupCreateInputPreventionWindow (GroupSelection *group)
2780 {
2781     if (!group->inputPrevention)
2782     {
2783 	XSetWindowAttributes attrib;
2784 	attrib.override_redirect = TRUE;
2785 
2786 	group->inputPrevention =
2787 	    XCreateWindow (group->screen->display->display,
2788 			   group->screen->root, -100, -100, 1, 1, 0,
2789 			   CopyFromParent, InputOnly,
2790 			   CopyFromParent, CWOverrideRedirect, &attrib);
2791 	group->ipwMapped = FALSE;
2792     }
2793 }
2794 
2795 /*
2796  * groupDestroyInputPreventionWindow
2797  *
2798  */
2799 void
groupDestroyInputPreventionWindow(GroupSelection * group)2800 groupDestroyInputPreventionWindow (GroupSelection *group)
2801 {
2802     if (group->inputPrevention)
2803     {
2804 	XDestroyWindow (group->screen->display->display,
2805 			group->inputPrevention);
2806 
2807 	group->inputPrevention = None;
2808 	group->ipwMapped = TRUE;
2809     }
2810 }
2811