// Tab.cc for Fluxbox Window Manager
// Copyright (c) 2001 - 2002 Henrik Kinnunen (fluxgen@linuxmail.org)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

// $Id: Tab.cc,v 1.20 2002/02/04 06:50:48 fluxgen Exp $

#include "Tab.hh"

#ifdef HAVE_CONFIG_H
#	include "../config.h"
#endif // HAVE_CONFIG_H

#include "i18n.hh"
#include "DrawUtil.hh"
#include "Screen.hh"

#include <iostream>
using namespace std;

bool Tab::m_stoptabs = false;
Tab::t_tabplacementlist Tab::m_tabplacementlist[] = {
		{PTOP, "Top"},
		{PBOTTOM, "Bottom"},
		{PLEFT, "Left"},
		{PRIGHT, "Right"},
		{PNONE, "none"}
	};

Tab::t_tabplacementlist Tab::m_tabalignmentlist[] = {
		{ALEFT, "Left"},
		{ACENTER, "Center"},
		{ARIGHT, "Right"},
		{ARELATIVE, "Relative"},
		{ANONE, "none"}
	};

Tab::Tab(FluxboxWindow *win, Tab *prev, Tab *next) { 
	//set default values
	
	m_focus = m_moving = false;
	m_configured = true; // only set to false before Fluxbox::reconfigure
	m_move_x = m_move_y = 0;	
	m_prev = prev; m_next = next; 
	m_win = win;
	m_display = Fluxbox::instance()->getXDisplay();
	
	if ((m_win->getScreen()->getTabPlacement() == PLEFT ||
			m_win->getScreen()->getTabPlacement() == PRIGHT) &&
			m_win->getScreen()->isTabRotateVertical()) {
		m_size_w = m_win->getScreen()->getTabHeight();
		m_size_h = m_win->getScreen()->getTabWidth();
	} else {
		m_size_w = m_win->getScreen()->getTabWidth();
		m_size_h = m_win->getScreen()->getTabHeight();
	}

	createTabWindow();

	calcIncrease();
}

Tab::~Tab() {

	disconnect();	
	
	Fluxbox::instance()->removeTabSearch(m_tabwin);
	XDestroyWindow(m_display, m_tabwin);
}


//----------------  createTabWindow ---------------
// (private)
// Creates the Window for tab to be above the title window.
// This should only be called by the constructor.
//-------------------------------------------------
void Tab::createTabWindow() {
	unsigned long attrib_mask = CWBackPixmap | CWBackPixel | CWBorderPixel |
			CWColormap | CWOverrideRedirect | CWEventMask;
	XSetWindowAttributes attrib;
	attrib.background_pixmap = None;
	attrib.background_pixel = attrib.border_pixel =
		m_win->getScreen()->getWindowStyle()->tab.border_color.getPixel();
	attrib.colormap = m_win->getScreen()->getColormap();
	attrib.override_redirect = True;
	attrib.event_mask = ButtonPressMask | ButtonReleaseMask |
		ButtonMotionMask | ExposureMask | EnterWindowMask;
	//Notice that m_size_w gets the TOTAL width of tabs INCLUDING borders
	m_tabwin = XCreateWindow(m_display, m_win->getScreen()->getRootWindow(), 
		-30000, -30000, //TODO: So that it wont flicker or
			// appear before the window do
		m_size_w - m_win->getScreen()->getWindowStyle()->tab.border_width_2x,
		m_size_h - m_win->getScreen()->getWindowStyle()->tab.border_width_2x,
			m_win->getScreen()->getWindowStyle()->tab.border_width,
			m_win->getScreen()->getDepth(), InputOutput,
			m_win->getScreen()->getVisual(), attrib_mask, &attrib);

	//set grab
	XGrabButton(m_display, Button1, Mod1Mask, m_tabwin, True,
		ButtonReleaseMask | ButtonMotionMask, GrabModeAsync,
		GrabModeAsync, None, Fluxbox::instance()->getMoveCursor());

	//save to tabsearch
	Fluxbox::instance()->saveTabSearch(m_tabwin, this);	

	XMapSubwindows(m_display, m_tabwin);

	XMapWindow(m_display, m_tabwin);

	decorate();
}

//-------------- focus --------------------
// Called when the focus changes in m_win 
// updates pixmap or color and draws the tab
//-----------------------------------------
void Tab::focus() {

	if (m_win->isFocused()) {
		if (m_focus_pm)
			XSetWindowBackgroundPixmap(m_display, m_tabwin, m_focus_pm);
		else
			XSetWindowBackground(m_display, m_tabwin, m_focus_pixel);
	} else {
		if (m_unfocus_pm)
			XSetWindowBackgroundPixmap(m_display, m_tabwin, m_unfocus_pm);
		else
			XSetWindowBackground(m_display, m_tabwin, m_unfocus_pixel);
	}
	XClearWindow(m_display, m_tabwin);	
	draw(false);
}

//-------------- raise --------------------
// Raises the tabs in the tablist
//-----------------------------------------
void Tab::raise() {
	//get first tab
	Tab *tab = 0;
	//raise tabs
	for (tab = getFirst(this); tab!=0; tab = tab->m_next)
		m_win->getScreen()->raiseWindows(&tab->m_tabwin, 1);
}

//-------------- lower --------------------
// Lowers the tabs in the tablist AND
// the windows the tabs relate to
//-----------------------------------------
void Tab::lower() {
	Tab *current = this;
	FluxboxWindow *win = 0; //convenience
	//this have to be done in the correct order, otherwise we'll switch the window
	//being ontop in the group
	do { 
		XLowerWindow(m_display, current->m_tabwin); //lower tabwin and tabs window
		win = current->getWindow(); 
		win->getScreen()->getWorkspace(win->getWorkspaceNumber())->lowerWindow(win);

		current = current->next(); //get next
		if (current == 0)
			current = getFirst(this); //there weren't any after, get the first

	} while (current != this);
}

//-------------- loadTheme -----------------
// loads the texture with the correct
// width and height, this is necessary in
// vertical and relative tab modes
// TODO optimize this
//------------------------------------------
void Tab::loadTheme() {
	BImageControl *image_ctrl = m_win->getScreen()->getImageControl();
	Pixmap tmp = m_focus_pm;
	BTexture *texture = &(m_win->getScreen()->getWindowStyle()->tab.l_focus);

	if (texture->getTexture() & BImage::PARENTRELATIVE ) {
		BTexture *pt = &(m_win->getScreen()->getWindowStyle()->tab.t_focus);
		if (pt->getTexture() == (BImage::FLAT | BImage::SOLID)) {
			m_focus_pm = None;
			m_focus_pixel = pt->getColor()->getPixel();
		} else
			m_focus_pm =
				image_ctrl->renderImage(m_size_w, m_size_h, pt);

		if (tmp) image_ctrl->removeImage(tmp);

	} else {
		if (texture->getTexture() == (BImage::FLAT | BImage::SOLID)) {
			m_focus_pm = None;
			m_focus_pixel = texture->getColor()->getPixel();
		} else
			m_focus_pm =
				image_ctrl->renderImage(m_size_w, m_size_h, texture);
		if (tmp) image_ctrl->removeImage(tmp);
	}

	tmp = m_unfocus_pm;
	texture = &(m_win->getScreen()->getWindowStyle()->tab.l_unfocus);

	if (texture->getTexture() & BImage::PARENTRELATIVE ) {
		BTexture *pt = &(m_win->getScreen()->getWindowStyle()->tab.t_unfocus);
		if (pt->getTexture() == (BImage::FLAT | BImage::SOLID)) {
			m_unfocus_pm = None;
			m_unfocus_pixel = pt->getColor()->getPixel();
		} else
			m_unfocus_pm =
			image_ctrl->renderImage(m_size_w, m_size_h, pt);
	} else {
		if (texture->getTexture() == (BImage::FLAT | BImage::SOLID)) {
			m_unfocus_pm = None;
			m_unfocus_pixel = texture->getColor()->getPixel();
		} else
			m_unfocus_pm =
				image_ctrl->renderImage(m_size_w, m_size_h, texture);
	}
	
	if (tmp) image_ctrl->removeImage(tmp);
}

//-------------- decorate --------------------
// decorates the tab with current theme
//--------------------------------------------
void Tab::decorate() {
	loadTheme();

	XSetWindowBorderWidth(m_display, m_tabwin,
		m_win->getScreen()->getWindowStyle()->tab.border_width);
	XSetWindowBorder(m_display, m_tabwin,
		m_win->getScreen()->getWindowStyle()->tab.border_color.getPixel());
}

//-------------- deiconify -----------------
// Deiconifies the tab
// Used from FluxboxWindow to deiconify the tab when the window is deiconfied
//------------------------------------------
void Tab::deiconify() {
	XMapWindow(m_display, m_tabwin);
}

//------------- iconify --------------------
// Iconifies the tab.
// Used from FluxboxWindow to hide tab win when window is iconified
// disconnects itself from the list
//------------------------------------------
void Tab::iconify() {
	disconnect();
	withdraw();
}

//------------ withdraw --------------
// Unmaps the tab from display
//------------------------------------
void Tab::withdraw() {
	XUnmapWindow(m_display, m_tabwin);	
}

//------------ stick --------------------
// Set/reset the the sticky on all windows in the list
//---------------------------------------
void Tab::stick() {
	Tab *tab;
 
	//now do stick for all windows in the list
	for (tab = getFirst(this); tab != 0; tab = tab->m_next) {
		FluxboxWindow *win = tab->m_win; //just for convenience
		if (win->isStuck()) {
			win->blackbox_attrib.flags ^= BaseDisplay::ATTRIB_OMNIPRESENT;
			win->blackbox_attrib.attrib ^= BaseDisplay::ATTRIB_OMNIPRESENT;
			win->stuck = false;
			if (!win->isIconic())
				win->getScreen()->reassociateWindow(win, -1, true);

		} else {
			win->stuck = true;
			win->blackbox_attrib.flags |= BaseDisplay::ATTRIB_OMNIPRESENT;
			win->blackbox_attrib.attrib |= BaseDisplay::ATTRIB_OMNIPRESENT;
		}
		
		win->setState(win->current_state);
	}	

}

//------------- resize -------------
// Resize the window's in the tablist
//----------------------------------
void Tab::resize() {
	Tab *tab;

	//now move and resize the windows in the list
	for (tab = getFirst(this); tab != 0; tab = tab->m_next) {
		if (tab!=this) {
			tab->m_win->configure(m_win->getXFrame(), m_win->getYFrame(),
								m_win->getWidth(), m_win->getHeight());
		}
	}

	// need to resize tabs if in relative mode
	if (m_win->getScreen()->getTabAlignment() == ARELATIVE) {
		calcIncrease();
		setPosition();
	}
}

//----------- shade --------------
// Shades the windows in the tablist
//--------------------------------
void Tab::shade() {
	Tab *tab;

	for(tab = getFirst(this); tab != 0; tab = tab->m_next) {
		if (tab==this)
			continue;
		tab->m_win->shade();
	}

	if (m_win->getScreen()->getTabPlacement() == PLEFT ||
			m_win->getScreen()->getTabPlacement() == PRIGHT) { 
		resizeGroup();
		calcIncrease();
	}

	if (!(m_win->getScreen()->getTabPlacement() == PTOP))
		setPosition();
}

//------------ draw -----------------
// Draws the tab 
// if pressed = true then it draws the tab in pressed 
// mode else it draws it in normal mode
// TODO: the "draw in pressed mode" 
//-----------------------------------
void Tab::draw(bool pressed) {	
	unsigned int tabtext_w;

	GC gc = ((m_win->isFocused()) ? m_win->getScreen()->getWindowStyle()->tab.l_text_focus_gc :
		m_win->getScreen()->getWindowStyle()->tab.l_text_unfocus_gc);

	// Different routines for drawing rotated text
	if ((m_win->getScreen()->getTabPlacement() == PLEFT ||
			m_win->getScreen()->getTabPlacement() == PRIGHT) &&
			(!m_win->isShaded() && m_win->getScreen()->isTabRotateVertical())) {

		tabtext_w = DrawUtil::XRotTextWidth(m_win->getScreen()->getWindowStyle()->tab.rot_font,
						m_win->client.title, m_win->client.title_len);
		tabtext_w += (m_win->frame.bevel_w * 4);

		DrawUtil::DrawRotString(m_display, m_tabwin, gc,
				m_win->getScreen()->getWindowStyle()->tab.rot_font,
				m_win->getScreen()->getWindowStyle()->tab.font.justify,
				tabtext_w, m_size_w, m_size_h,
				m_win->frame.bevel_w, m_win->client.title);

	} else {
		if (I18n::instance()->multibyte()) { // TODO: maybe move this out from here?
			XRectangle ink, logical;
			XmbTextExtents(m_win->getScreen()->getWindowStyle()->tab.font.set,
					m_win->client.title, m_win->client.title_len,
					&ink, &logical);
			tabtext_w = logical.width;
		} else {
			tabtext_w = XTextWidth(
					m_win->getScreen()->getWindowStyle()->tab.font.fontstruct,
					m_win->client.title, m_win->client.title_len);
		}
		tabtext_w += (m_win->frame.bevel_w * 4);

		DrawUtil::DrawString(m_display, m_tabwin, gc,
				&m_win->getScreen()->getWindowStyle()->tab.font,
				tabtext_w, m_size_w,
				m_win->frame.bevel_w, m_win->client.title);
	}
}

//-----------------------------------------------
//Helper for the Tab::setPosition() call
//returns the y position component correctly
//according to shading in cases PBOTTOM and 
//isShaded()
//-----------------------------------------------
int Tab::setPositionShadingHelper(bool shaded) {
	if (shaded) {
		return m_win->getYFrame() + m_win->getTitleHeight() + 
				m_win->getScreen()->getBorderWidth2x();
	} else {
		return m_win->getYFrame() + m_win->getHeight() +
				m_win->getScreen()->getBorderWidth2x();
	}
}

//-----------------------------------------------
//Helpers for correct alignment of tabs used
//by the setPosition() call
//return x/y positions correctly according to
//alignment, the 1st for cases PTOP and PBOTTOM
//the 2nd for cases PLEFT and PRIGHT
//-----------------------------------------------
int Tab::setPositionTBAlignHelper(Alignment align) {
	switch(align) {

	case ARELATIVE:
	case ALEFT:
		return m_win->getXFrame(); 
		break;
	case ACENTER:
		return calcCenterXPos(); 
		break;
	case ARIGHT:
		return m_win->getXFrame() + m_win->getWidth() +
			m_win->getScreen()->getBorderWidth2x() - m_size_w;
	default:
#ifdef DEBUG
		cerr << __FILE__ << ":" <<__LINE__ << ": " <<
			"Unsupported Alignment" << endl;
#endif //DEBUG
		return 0;
		break;
	}
}

int Tab::setPositionLRAlignHelper(Alignment align) {
	switch(align) {
	case ALEFT:
		return m_win->getYFrame() - m_size_h + m_win->getHeight() +
				m_win->getScreen()->getBorderWidth2x();
		break;
	case ACENTER:
		return calcCenterYPos();
		break;
	case ARELATIVE:
	case ARIGHT:
		return m_win->getYFrame();
		break;
	default:
#ifdef DEBUG
		cerr << __FILE__ << ":"<< __LINE__ << ": " <<
			"Unsupported Alignment" << endl;
#endif //DEBUG
		return 0;
		break;
	}
}

//------------- setPosition -----------------
// Position tab ( follow the m_win pos ).
// (and resize)
// Set new position of the other tabs in the chain
//-------------------------------------------
void Tab::setPosition() {	
	//don't do anything if the tablist is freezed
	if (m_stoptabs)
		return;

	Tab *tab;
	int pos_x = 0, pos_y = 0;
	
	m_stoptabs = true; //freeze tablist
	
	//and check for max tabs

	//Tab placement + alignment
	switch (m_win->getScreen()->getTabPlacement()) {
	case PTOP:
		pos_y = m_win->getYFrame() - m_size_h;
		pos_x = setPositionTBAlignHelper(
				m_win->getScreen()->getTabAlignment());
		break;
	case PBOTTOM:
		pos_y = setPositionShadingHelper(m_win->isShaded());
		pos_x = setPositionTBAlignHelper(
				m_win->getScreen()->getTabAlignment());
		break;
	case PLEFT:
		pos_x = m_win->isShaded() ?
			setPositionTBAlignHelper(m_win->getScreen()->getTabAlignment()) :
			m_win->getXFrame() - m_size_w;
		pos_y = m_win->isShaded() ?
			setPositionShadingHelper(true) :
			setPositionLRAlignHelper(m_win->getScreen()->getTabAlignment());
		break;
	case PRIGHT:
		pos_x = m_win->isShaded() ?
			setPositionTBAlignHelper(m_win->getScreen()->getTabAlignment()) :
			m_win->getXFrame() + m_win->getWidth() +
				m_win->getScreen()->getBorderWidth2x();
		pos_y = m_win->isShaded() ?
			setPositionShadingHelper(true) :
			setPositionLRAlignHelper(m_win->getScreen()->getTabAlignment());
		break;
	default:
		if(m_win->isShaded()) {
			pos_y = setPositionShadingHelper(true);
			pos_x = setPositionTBAlignHelper(
				m_win->getScreen()->getTabAlignment());
		} else {
			setPositionShadingHelper(false);
		}
		break;
	}

	for (tab = getFirst(this);
			tab!=0; 
			pos_x += tab->m_inc_x, pos_y += tab->m_inc_y,
			tab = tab->m_next){
		
		XMoveWindow(m_display, tab->m_tabwin, pos_x, pos_y);
				
		//dont move FluxboxWindow if the iterator = this
		if (tab!=this) {
			tab->m_win->configure(m_win->getXFrame(), m_win->getYFrame(), 
					m_win->getWidth(), m_win->getHeight());
		}	
	}	
	
	m_stoptabs = false;//thaw tablist
}

//------------- calcIncrease ----------------
// calculates m_inc_x and m_inc_y for tabs
// used for positioning the tabs.
//-------------------------------------------
void Tab::calcIncrease(void) {
	#ifdef DEBUG
	cerr << "Calculating tab increase" << endl;
	#endif // DEBUG

	Tab *tab;
	int inc_x = 0, inc_y = 0;
	unsigned int i = 0, tabs = numObjects();

	if (m_win->getScreen()->getTabPlacement() == PTOP ||
			m_win->getScreen()->getTabPlacement() == PBOTTOM ||
			m_win->isShaded()) {
		inc_y = 0;

		switch(m_win->getScreen()->getTabAlignment()) {
			case ALEFT:
				inc_x = m_size_w;
				break;
			case ACENTER:
				inc_x = m_size_w;
				break;
			case ARIGHT:
				inc_x = -m_size_w;
				break;
			case ARELATIVE:
				inc_x = calcRelativeWidth();
				break;
			default:
				break;
		}
	} else if (m_win->getScreen()->getTabPlacement() == PLEFT ||
					m_win->getScreen()->getTabPlacement() == PRIGHT) {
		inc_x = 0;

		switch(m_win->getScreen()->getTabAlignment()) {
			case ALEFT:
				inc_y = -m_size_h;
				break;
			case ACENTER:
				inc_y = m_size_h;
				break;
			case ARIGHT:
				inc_y = m_size_h;
				break;
			case ARELATIVE:
				inc_y = calcRelativeHeight();
				break;
			default:
				break;
		}
	}

	for (tab = getFirst(this); tab!=0; tab = tab->m_next, i++) {

	//TODO: move this out from here?
		if ((m_win->getScreen()->getTabPlacement() == PTOP ||
				m_win->getScreen()->getTabPlacement() == PBOTTOM ||
				m_win->isShaded()) &&
				m_win->getScreen()->getTabAlignment() == ARELATIVE) {
			if (!((m_win->getWidth() +
					m_win->getScreen()->getBorderWidth2x()) % tabs) ||
					i >= ((m_win->getWidth() +
					m_win->getScreen()->getBorderWidth2x()) % tabs)) {
				tab->setTabWidth(inc_x);
				tab->m_inc_x = inc_x;
			} else { // adding 1 extra pixel to get tabs like win width
				tab->setTabWidth(inc_x + 1);
				tab->m_inc_x = inc_x + 1;
			}
			tab->m_inc_y = inc_y;
		} else if (m_win->getScreen()->getTabAlignment() == ARELATIVE) {
			if (!((m_win->getHeight() +
				m_win->getScreen()->getBorderWidth2x()) % tabs) ||
				i >= ((m_win->getHeight() +
				m_win->getScreen()->getBorderWidth2x()) % tabs)) {

				tab->setTabHeight(inc_y);
				tab->m_inc_y = inc_y;
			} else {
				// adding 1 extra pixel to get tabs match window width
				tab->setTabHeight(inc_y + 1);
				tab->m_inc_y = inc_y + 1;
			}
			tab->m_inc_x = inc_x;
		} else { // non relative modes
			tab->m_inc_x = inc_x;
			tab->m_inc_y = inc_y;
		}
	}
}

//------------- buttonPressEvent -----------
// Handle button press event here.
//------------------------------------------
void Tab::buttonPressEvent(XButtonEvent *be) {	
	//draw in pressed mode
	draw(true);
	
	//set window to titlewindow so we can take advatage of drag function
	be->window = m_win->frame.title;
	
	//call windows buttonpress eventhandler
	m_win->buttonPressEvent(be);
}

//----------- buttonReleaseEvent ----------
// Handle button release event here.
// If tab is dropped then it should try to find
// the window where the tab where dropped.
//-----------------------------------------
void Tab::buttonReleaseEvent(XButtonEvent *be) {		
	
	if (m_moving) {
		
		m_moving = false; 
		
		//erase tabmoving rectangle
		XDrawRectangle(m_display, m_win->getScreen()->getRootWindow(),
			m_win->getScreen()->getOpGC(),
			m_move_x, m_move_y, 
			m_size_w, m_size_h);
		
		Fluxbox::instance()->ungrab();
		XUngrabPointer(m_display, CurrentTime);
		
		//storage of window and pos of window where we dropped the tab
		Window child;
		int dest_x = 0, dest_y = 0;
		
		//find window on coordinates of buttonReleaseEvent
		if (XTranslateCoordinates(m_display, m_win->getScreen()->getRootWindow(), 
							m_win->getScreen()->getRootWindow(),
							be->x_root, be->y_root, &dest_x, &dest_y, &child)) {
			
			Tab *tab = Fluxbox::instance()->searchTab(child);
			FluxboxWindow *win = Fluxbox::instance()->searchWindow(child);
			//search tablist for a tabwindow
			if ( (tab!=0) || (m_win->getScreen()->isSloppyWindowGrouping() &&
					(win!=0) && (tab = win->getTab())!=0)) {

				if (tab == this) // inserting ourself to ourself causes a disconnect
					return;

				// do only attach a hole chain if we dropped the
				// first tab in the dropped chain...
				if (m_prev)
					disconnect();
				
				// attach this tabwindow chain to the tabwindow chain we found.
				tab->insert(this);

			} else {	
				disconnect();

				// convenience
				unsigned int placement = m_win->getScreen()->getTabPlacement();

				// (ab)using dest_x and dest_y
				dest_x = be->x_root;
				dest_y = be->y_root;

				if (placement == PTOP || placement == PBOTTOM || m_win->isShaded()) {
					if (placement == PBOTTOM && !m_win->isShaded())
						dest_y -= m_win->getHeight();
					else if (placement != PTOP && m_win->isShaded())
						dest_y -= m_win->getTitleHeight();
					else // PTOP
						dest_y += m_win->getTitleHeight();

					switch(m_win->getScreen()->getTabAlignment()) {
						case ACENTER:
							dest_x -= (m_win->getWidth() / 2) - (m_size_w / 2);
							break;
						case ARIGHT:
							dest_x -= m_win->getWidth() - m_size_w;
							break;
						default:
							break;
					}

				} else { // PLEFT & PRIGHT
					if (placement == PRIGHT)
						dest_x = be->x_root - m_win->getWidth();

					switch(m_win->getScreen()->getTabAlignment()) {
						case ACENTER:
							dest_y -= (m_win->getHeight() / 2) - (m_size_h / 2);
							break;
						case ALEFT:
							dest_y -= m_win->getHeight() - m_size_h;
							break;
						default:
							break;
					}
				}
				//TODO: this causes an calculate increase event, even if we
				// only are moving a window
				m_win->configure(dest_x, dest_y, m_win->getWidth(), m_win->getHeight());
			}
		}
	} else {
		
		//raise this tabwindow
		raise();
		
		//set window to title window soo we can use m_win handler for menu
		be->window = m_win->frame.title;
		
		//call windows buttonrelease event handler so it can popup a menu if needed
		m_win->buttonReleaseEvent(be);
	}
	
}

//------------- exposeEvent ------------
// Handle expose event here.
// Draws the tab unpressed
//--------------------------------------
void Tab::exposeEvent(XExposeEvent *ee) {
	draw(false);
}

//----------- motionNotifyEvent --------
// Handles motion event here
// Draws the rectangle of moving tab
//--------------------------------------
void Tab::motionNotifyEvent(XMotionEvent *me) {
	
	Fluxbox *fluxbox = Fluxbox::instance();
	
	//if mousebutton 2 is pressed
	if (me->state & Button2Mask) {
		if (!m_moving) {
			m_moving = true; 
			
			XGrabPointer(m_display, me->window, False, Button2MotionMask |
								ButtonReleaseMask, GrabModeAsync, GrabModeAsync,
								None, fluxbox->getMoveCursor(), CurrentTime);

			fluxbox->grab();

			m_move_x = me->x_root - 1;
			m_move_y = me->y_root - 1;
				
			XDrawRectangle(m_display, m_win->getScreen()->getRootWindow(), 
					m_win->getScreen()->getOpGC(),
					m_move_x, m_move_y,
					m_size_w, m_size_h);
	
		} else {
		
			int dx = me->x_root - 1, dy = me->y_root - 1;

			dx -= m_win->getScreen()->getBorderWidth();
			dy -= m_win->getScreen()->getBorderWidth();

			if (m_win->getScreen()->getEdgeSnapThreshold()) {
				int drx = m_win->getScreen()->getWidth() - (dx + 1);

				if (dx > 0 && dx < drx && dx < m_win->getScreen()->getEdgeSnapThreshold()) 
					dx = 0;
				else if (drx > 0 && drx < m_win->getScreen()->getEdgeSnapThreshold())
					dx = m_win->getScreen()->getWidth() - 1;

				int dtty, dbby, dty, dby;
		
				switch (m_win->getScreen()->getToolbarPlacement()) {
				case Toolbar::TOPLEFT:
				case Toolbar::TOPCENTER:
				case Toolbar::TOPRIGHT:
					dtty = m_win->getScreen()->getToolbar()->getExposedHeight() +
							m_win->getScreen()->getBorderWidth();
					dbby = m_win->getScreen()->getHeight();
					break;

				default:
					dtty = 0;
					dbby = m_win->getScreen()->getToolbar()->getY();
					break;
				}
		
				dty = dy - dtty;
				dby = dbby - (dy + 1);

				if (dy > 0 && dty < m_win->getScreen()->getEdgeSnapThreshold())
					dy = dtty;
				else if (dby > 0 && dby < m_win->getScreen()->getEdgeSnapThreshold())
					dy = dbby - 1;
		
			}
		
			//erase rectangle
			XDrawRectangle(m_display, m_win->getScreen()->getRootWindow(),
					m_win->getScreen()->getOpGC(),
					m_move_x, m_move_y, 
					m_size_w, m_size_h);

			//redraw rectangle at new pos
			m_move_x = dx;
			m_move_y = dy;			
			XDrawRectangle(m_display, m_win->getScreen()->getRootWindow(), 
					m_win->getScreen()->getOpGC(),
					m_move_x, m_move_y,
					m_size_w, m_size_h);					
			
		}
	} 
}

//-------------- getFirst() ---------
// Returns the first Tab in the chain 
// of currentchain. 
//-----------------------------------
Tab *Tab::getFirst(Tab *current) {
	if (!current)
		return 0;
		
	Tab *i=current;
	
	for (; i->m_prev != 0; i = i->m_prev);
	return i;
}

//-------------- getLast() ---------
// Returns the last Tab in the chain 
// of currentchain. 
//-----------------------------------
Tab *Tab::getLast(Tab *current) {
	if (!current)
		return 0;
	Tab *i=current;
	
	for (; i->m_next != 0; i = i->m_next);
	return i;
}

//-------------- insert ------------
// (private)
// Inserts a tab in the chain
//----------------------------------
void Tab::insert(Tab *tab) {
	
	if (!tab || tab == this) //dont insert if the tab = 0 or the tab = this
		return;

	Tab *first = getFirst(this);
	
	//if the tab already in chain then disconnect it
	for (; first!=0; first = first->m_next) {
		if (first==tab) {
			#ifdef DEBUG
			cerr<<"Tab already in chain. Disconnecting!"<<endl;			
			#endif // DEBUG
			tab->disconnect();
			break;
		}
	}

	//get last tab in the chain to be inserted
	Tab *last = tab;
	for (; last->m_next!=0; last=last->m_next); 
	//do sticky before we connect it to the chain
	//sticky bit on window
	if (m_win->isStuck() && !tab->m_win->isStuck() ||
			!m_win->isStuck() && tab->m_win->isStuck())
			tab->m_win->stick(); //this will set all the m_wins in the list
	
	//connect the tab to this chain
	
	if (m_next)	
		m_next->m_prev = last;
	tab->m_prev = this; 
	last->m_next = m_next; 

	m_next = tab;	

	bool resize_tabs = false;

	//TODO: cleanup and optimize
	//move and resize all windows in the tablist we inserted
	//only from first tab of the inserted chain to the last
	for (; tab!=last->m_next; tab=tab->m_next) {
		if (m_win->isShaded() != tab->m_win->isShaded()) {
			tab->m_stoptabs = true; // we don't want any actions performed on the
															// tabs, just the tab windows!
			if (m_win->getScreen()->getTabPlacement() == PLEFT ||
					m_win->getScreen()->getTabPlacement() == PRIGHT)
				resize_tabs = true;

			// if the window we are grouping to, we need to shade the tab window
			// _after_ reconfigure
			if(m_win->isShaded()) {
					tab->m_win->configure(m_win->getXFrame(), m_win->getYFrame(),
							m_win->getWidth(), m_win->getHeight());
					tab->m_win->shade();
			} else {
					tab->m_win->shade(); // switch to correct shade state
					tab->m_win->configure(m_win->getXFrame(), m_win->getYFrame(),
						m_win->getWidth(), m_win->getHeight());
			}

			tab->m_stoptabs = false;

		// both window have the same shaded state and have different sizes,
		// checking this so that I'll only do shade on windows if configure did
		// anything.
		} else if ((m_win->getWidth() != tab->m_win->getWidth()) ||
				(m_win->getHeight() != tab->m_win->getHeight())) {

			tab->m_win->configure(m_win->getXFrame(), m_win->getYFrame(),
				m_win->getWidth(), m_win->getHeight());

			// need to shade the tab window as configure will mess it up
			if (m_win->isShaded())
				tab->m_win->shade();
		}
	}

	// resize if in relative mode or resize_tabs is true
	if(m_win->getScreen()->getTabAlignment() == ARELATIVE ||
			resize_tabs) {
		resizeGroup();
		calcIncrease();
	}
	// reposition tabs
	setPosition();
}

//---------- disconnect() --------------
// Disconnects the tab from any chain
//--------------------------------------
void Tab::disconnect() {
	Tab *tmp = 0;
	if (m_prev) {	//if this have a chain to "the left" (previous tab) then set it's next to this next
		m_prev->m_next = m_next;
		tmp = m_prev;
	} 
	if (m_next) {	//if this have a chain to "the right" (next tab) then set it's prev to this prev
		m_next->m_prev = m_prev;
		tmp = m_next;
	}

	//mark as no chain, previous and next.
	m_prev = 0;
	m_next = 0;
	
	//reposition the tabs
	if (tmp) {
		if (m_win->getScreen()->getTabAlignment() == ARELATIVE)
			tmp->calcIncrease();
		tmp->setPosition();
	}

	if (m_win->getScreen()->getTabAlignment() == ARELATIVE)
		calcIncrease();
	
	setPosition();
}

// ------------ setTabWidth --------------
// Sets Tab width _including_ borders
// ---------------------------------------
void Tab::setTabWidth(unsigned int w) {
	if (w > m_win->getScreen()->getWindowStyle()->tab.border_width_2x &&
			w != m_size_w) {
		m_size_w = w;
		XResizeWindow(m_display, m_tabwin,
			m_size_w - m_win->getScreen()->getWindowStyle()->tab.border_width_2x,
			m_size_h - m_win->getScreen()->getWindowStyle()->tab.border_width_2x);

		loadTheme(); // rerender themes to right size
		focus(); // redraw the window
	}
}

// ------------ setTabHeight ---------
// Sets Tab height _including_ borders
// ---------------------------------------
void Tab::setTabHeight(unsigned int h) {
	if (h > m_win->getScreen()->getWindowStyle()->tab.border_width_2x &&
			h != m_size_h) {
		m_size_h = h;
		XResizeWindow(m_display, m_tabwin, 
			m_size_w - m_win->getScreen()->getWindowStyle()->tab.border_width_2x,
			m_size_h - m_win->getScreen()->getWindowStyle()->tab.border_width_2x);

		loadTheme(); // rerender themes to right size
		focus(); // redraw the window
	} 
}

// ------------ resizeGroup --------------
// This function is used when (un)shading
// to get right size/width of tabs when
// PLeft || PRight && isTabRotateVertical
// ---------------------------------------
void Tab::resizeGroup(void) {
	#ifdef DEBUG
	cerr <<__FILE__<<"("<<__LINE__<<"): Resizing group"<<endl;
	#endif //DEBUG
	Tab *first;
	for (first = getFirst(this); first != 0; first = first->m_next) {
		if ((m_win->getScreen()->getTabPlacement() == PLEFT ||
				m_win->getScreen()->getTabPlacement() == PRIGHT) &&
				m_win->getScreen()->isTabRotateVertical() &&
				!m_win->isShaded()) {
			first->setTabWidth(m_win->getScreen()->getTabHeight());
			first->setTabHeight(m_win->getScreen()->getTabWidth());
		} else {
			first->setTabWidth(m_win->getScreen()->getTabWidth());
			first->setTabHeight(m_win->getScreen()->getTabHeight());
		}
		//TODO: do I have to set this all the time?
		first->m_configured = true; //used in Fluxbox::reconfigure()
	}
}

//------------- calcRelativeWidth --------
// Returns: Calculated width for relative 
// alignment
//----------------------------------------
unsigned int Tab::calcRelativeWidth() {
	unsigned int num=0;
	//calculate num objs in list (extract this to a function?)
	for (Tab *first=getFirst(this); first!=0; first=first->m_next, num++);	

	return ((m_win->getWidth() + m_win->getScreen()->getBorderWidth2x())/num);
}

//--------------- numObjects -------------------
// Returns the number of objects in
// the TabGroup. 
//-----------------------------------------------
unsigned int Tab::numObjects() {
	unsigned int num = 0;
	for (Tab *tab = getFirst(this); tab != 0; tab = tab->m_next, num++);
	return num;
}

//------------- calcRelativeHeight -------
// Returns: Calculated height for relative 
// alignment
//----------------------------------------
unsigned int Tab::calcRelativeHeight() {
	return ((m_win->getHeight() + 
			m_win->getScreen()->getBorderWidth2x())/numObjects());
}

//------------- calcCenterXPos -----------
// Returns: Calculated x position for 
// centered alignment
//----------------------------------------
unsigned int Tab::calcCenterXPos() {
	return (m_win->getXFrame() + ((m_win->getWidth() - 
			(m_size_w * numObjects())) / 2));
}

//------------- calcCenterYPos -----------
// Returns: Calculated y position for 
// centered alignment
//----------------------------------------
unsigned int Tab::calcCenterYPos() {
	return (m_win->getYFrame() + ((m_win->getHeight() - 
			(m_size_h * numObjects())) / 2));
}


//------- getTabPlacementString ----------
// Returns the tabplacement string of the 
// tabplacement number on success else 0.
//----------------------------------------
const char *Tab::getTabPlacementString(Tab::Placement placement) {	
	for (int i=0; i<(PNONE / 5); i++) {
		if (m_tabplacementlist[i] == placement)
			return m_tabplacementlist[i].string;
	}
	return 0;
}

//------- getTabPlacementNum -------------
// Returns the tabplacement number of the 
// tabplacement string on success else
// the type none on failure.
//----------------------------------------
Tab::Placement Tab::getTabPlacementNum(const char *string) {
	for (int i=0; i<(PNONE / 5); i ++) {
		if (m_tabplacementlist[i] == string) {
			return static_cast<Tab::Placement>(m_tabplacementlist[i].tp);
		}
	}
	return PNONE;
}

//------- getTabAlignmentString ----------
// Returns the tabplacement string of the 
// tabplacement number on success else 0.
//----------------------------------------
const char *Tab::getTabAlignmentString(Tab::Alignment alignment) {	
	for (int i=0; i<ANONE; i++) {
		if (m_tabalignmentlist[i] == alignment)
			return m_tabalignmentlist[i].string;
	}
	return 0;
}

//------- getTabAlignmentNum -------------
// Returns the tabplacement number of the 
// tabplacement string on success else
// the type none on failure.
//----------------------------------------
Tab::Alignment Tab::getTabAlignmentNum(const char *string) {
	for (int i=0; i<ANONE; i++) {
		if (m_tabalignmentlist[i] == string) {
			return static_cast<Tab::Alignment>(m_tabalignmentlist[i].tp);
		}
	}
	return ANONE;
}