// FbPager.cc for FbPager
// Copyright (c) 2003-2004 Henrik Kinnunen (fluxgen at users.sourceforge.net)
// 
// 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$

#include "FbPager.hh"

// fbtk utils
#include "FbTk/EventManager.hh"
#include "FbTk/App.hh"
#include "FbTk/Color.hh"
#include "FbTk/StringUtil.hh"

// client handlers
#include "Ewmh.hh"
#include "FluxboxHandler.hh"

#include "Workspace.hh"
#include "WindowHint.hh"
#include "FbAtoms.hh"
#include "ScaleWindowToWindow.hh"
#include "Resources.hh"
#include "RefBinder.hh"

// x headers
#include <X11/Xatom.h>
#include <X11/Xutil.h>

// std headers
#include <iostream>
#include <cstdio>
#include <cassert>
#include <functional>
#include <algorithm>

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

// copied from fluxbox code
#ifdef HAVE_SSTREAM
#include <sstream>
#define FB_istringstream istringstream
#define FB_ostringstream ostringstream
#elif HAVE_STRSTREAM 
#include <strstream>
#define FB_istringstream istrstream
#define FB_ostringstream ostrstream
#else
#error "You dont have sstream or strstream headers!"
#endif // HAVE_STRSTREAM

using namespace std;

namespace {

int handleXErrors(Display *disp, XErrorEvent *xerror) {
    return 0;
}

template <typename Container>
void destroyList(Container &c) {
    while (!c.empty()) {
        delete c.back();
        c.pop_back();
    }
}

// TODO: is this optimal? i dont think so, but it works so far
bool compareButtonQueues(std::string q1, std::string q2) {

    FbTk::StringUtil::removeFirstWhitespace(q1);
    FbTk::StringUtil::removeFirstWhitespace(q2);
    FbTk::StringUtil::removeTrailingWhitespace(q1);
    FbTk::StringUtil::removeTrailingWhitespace(q2);
    
    return q1 == q2;
}


// little helper to keep  win  visible in  bigwin 
void respectConstraints(int& x, int& y,
                        const FbTk::FbWindow& win, const FbTk::FbWindow& bigwin) {

    const int left = - static_cast<int>((3 * win.width()) / 4);
    const int right = bigwin.width() - win.width() / 4;
    const int top =  - static_cast<int>((3 * win.height()) / 4);
    const int bottom = bigwin.height() - win.height() / 4;

    // make sure window isnt dragged out of visible area of workspace
    if ( x < left || x > right )
        x = win.x();
    if ( y < top || y > bottom )
        y = win.y();
}

} // end anonymous namespace


namespace FbPager {


FbPager::FbPager(int screen_num, bool withdraw, 
                 bool use_fbhandler,
                 bool use_ewmhhandler,
                 bool show_resources,
                 const char *resource_filename):
    m_rootwin(screen_num),
    m_window(m_rootwin,
             0, 0, // pos
             64, 64,  // size
             // event mask
             ButtonPressMask | ButtonReleaseMask | ExposureMask |
             FocusChangeMask | StructureNotifyMask|
             SubstructureRedirectMask),
    m_curr_workspace(0),
    m_move_window(),
    m_resmanager(0, false),
    m_alpha(m_resmanager, 255,
            "fbpager.alpha",
            "FbPager.Alpha"),
    m_start_x(m_resmanager, 0,
              "fbpager.x",
              "FbPager.x"),
    m_start_y(m_resmanager, 0,
              "fbpager.y",
              "FbPager.y"),
    m_workspace_width(m_resmanager, 64,
                      "fbpager.workspace.width",
                      "FbPager.Workspace.Width"),
    m_workspace_height(m_resmanager, 64,
                       "fbpager.workspace.height",
                       "FbPager.Workspace.Height"),
    m_workspaces_per_row(m_resmanager, 6400, // big number...
                         "fbpager.workspacesPerRow",
                         "FbPager.WorkspacesPerRow"),
    m_follow_drag(m_resmanager, false,
                  "fbpager.followDrag",
                  "FbPager.FollowDrag"),
    m_follow_move(m_resmanager, false,
                  "fbpager.followMove",
                  "FbPager.FollowMove"),
    m_change_workspace_button(m_resmanager, "11", 
                              "fbpager.changeWorkspaceButton", 
                              "FbPager.ChangeWorkspaceButton"),
    m_raise_window_button(m_resmanager, "2", 
                          "fbpager.raiseWindowButton", 
                          "FbPager.RaiseWindowButton"),
    m_lower_window_button(m_resmanager, "3", 
                          "fbpager.lowerWindowButton", 
                          "FbPager.LowerWindowButton"),
    m_close_window_button(m_resmanager, "3 3 1",
                          "fbpager.closeWindowButton",
                          "FbPager.CloseWindowButton"),
    m_exit_button(m_resmanager, "1 3 3",
                  "fbpager.exitButton",
                  "FbPager.ExitButton"),
    m_next_workspace_button(m_resmanager, "4",
                            "fbpager.nextWorkspaceButton",
                            "FbPager.NextWorkspaceButton"),
    m_prev_workspace_button(m_resmanager, "5",
                            "fbpager.prevWorkspaceButton",
                            "FbPager.PrevWorkspaceButton"),
    m_move_in_workspace_button(m_resmanager, 1,
                               "fbpager.moveInWorkspaceButton",
                               "FbPager.MoveInWorkspaceButton"),
    m_drag_to_workspace_button(m_resmanager, 2,
                               "fbpager.dragToWorkspaceButton",
                               "FbPager.DragToWorkspaceButtton"),
    m_align(m_resmanager, LEFT_TO_RIGHT, 
            "fbpager.align", 
            "FbPager.Align"),
    m_color_str(m_resmanager, "white",
                "fbpager.color",
                "FbPager.Color"),
    m_window_color_str(m_resmanager, "white", 
                       "fbpager.windowColor", 
                       "FbPager.WindowColor"),
    m_focused_window_color_str(m_resmanager, "white",
                               "fbpager.focusedWindowColor",
                               "FbPager.FocusedWindowColor"),
    m_window_bordercolor_str(m_resmanager, "black", 
                             "fbpager.windowBorderColor", 
                             "FbPager.WindowBorderColor"),
    m_background_color_str(m_resmanager, "darkgray", 
                           "fbpager.backgroundColor", 
                           "FbPager.BackgroundColor"),
    m_current_background_color_str(m_resmanager, "lightgray", 
                                   "fbpager.currentBackgroundColor", 
                                   "FbPager.CurrentBackgroundColor"),
    m_multiclick_time(m_resmanager, 250,
                      "fbpager.multiClickTime",
                      "FbPager.MultiClickTime"),
    m_window_icons(m_resmanager, false,
                   "fbpager.icons",
                   "FbPager.Icons"),
    m_window_border_width(m_resmanager, 1,
                          "fbpager.windowBorderWidth",
                          "FbPager.WindowBorderWidth"),
    m_last_workspace_num(-1) {


    if (use_fbhandler)
        m_clienthandlers.push_back(new FluxboxHandler());

    if (use_ewmhhandler) 
        m_clienthandlers.push_back(new Ewmh());

    XSetErrorHandler(handleXErrors);

    FbTk::EventManager::instance()->add(*this, m_window);

    // setup client window   
    m_window.setBackgroundColor(FbTk::Color((*m_color_str).c_str(), m_window.screenNumber()));
    m_window.setAlpha(*m_alpha);


    Display *disp = FbTk::App::instance()->display();
    Atom wmproto[2];
    int wmprot_size = 1;
    wmproto[0] = XInternAtom(disp, "WM_DELETE_WINDOW", False);
    if (FluxboxHandler::handler() != 0) {
        wmprot_size = 2;
        wmproto[1] = FbAtoms::instance()->getFluxboxStructureMessagesAtom();
    }
    XSetWMProtocols(disp, m_window.window(), wmproto, wmprot_size);


    XWMHints wmhints;

    if (!withdraw) {
        WindowHint hint;
        hint.add(WindowHint::WHINT_NO_DECOR | WindowHint::WHINT_SKIP_TASKBAR |
                 WindowHint::WHINT_STICKY | WindowHint::WHINT_SKIP_PAGER | 
                 WindowHint::WHINT_HIDDEN | WindowHint::WHINT_TYPE_DOCK);
        ClientHandlerList::iterator it = m_clienthandlers.begin();
        ClientHandlerList::iterator it_end = m_clienthandlers.end();
        for (; it != it_end; ++it)
            (*it)->setHints(m_window, hint);

        wmhints.initial_state = NormalState;
    } else // withdraw
        wmhints.initial_state = WithdrawnState;


    // setup no input
    wmhints.input = False;
    // set which hints we're using
    wmhints.flags = StateHint | InputHint;


    // set window name
    char *name = "FbPager";
    XTextProperty windowname;
    XStringListToTextProperty(&name, 1, &windowname);

    // setup class hints
    XClassHint classhints;
    classhints.res_name="fbpager";
    classhints.res_class="FbPager";
    // set window properties
    XSetWMProperties(disp, m_window.window(), &windowname, 0, 0, 0,
                     0, &wmhints, &classhints);

    if (resource_filename != 0)
        load(resource_filename);
    
    // setup workspace count
    ClientHandlerList::iterator it = m_clienthandlers.begin();
    ClientHandlerList::iterator it_end = m_clienthandlers.end();
    int workspaces = 0;
    for (; it != it_end; ++it) {
        workspaces = max((*it)->numberOfWorkspaces(m_window.screenNumber()), workspaces);
    }

    updateWorkspaceCount(workspaces);
    // finaly show window
    show();
    m_window.move(*m_start_x, *m_start_y);
    m_window.resize(m_window.width(), m_window.height());

    m_last_button_event.time= 0;

    if (show_resources) {
        // dump resources to stdout
        m_resmanager.dump(true);
    }
}

FbPager::~FbPager() {
    destroyList(m_clienthandlers);
    destroyList(m_workspaces);
}

void FbPager::show() {
    m_window.show();
}

void FbPager::hide() {
    m_window.hide();
}

void FbPager::handleEvent(XEvent &event) {
    // here we handle events that're not in FbTk::EventHandler interface

    if (event.type == ClientMessage)
        clientMessageEvent(event.xclient);
    else if (event.type == ConfigureNotify) {
        if (event.xconfigure.window != m_window.window()) {
            updateWindowHints(event.xconfigure.window);
        } else if (event.xconfigure.send_event == True){
            if (event.xconfigure.x != m_window.x() ||
                event.xconfigure.y != m_window.y() ||
                event.xconfigure.width != m_window.width() ||
                event.xconfigure.height != m_window.height()) {

                m_window.updateGeometry(event.xconfigure.x,
                                        event.xconfigure.y,
                                        event.xconfigure.width, 
                                        event.xconfigure.height);

                clearWindows(); // update transparency
            }

        }
    } else if (event.type == PropertyNotify) {
        propertyEvent(event.xproperty);
    } else {

    }
}

void FbPager::buttonPressEvent(XButtonEvent &event) {
    
    if ( event.time - m_last_button_event.time > 1000 )
        m_button_queue= "";
   
    FB_ostringstream os;

    // double click
    if (event.time - m_last_button_event.time < *m_multiclick_time &&
        event.button == m_last_button_event.button)
        os << event.button;
    else
        os << " " << event.button;

    // append button string to current queue
    m_button_queue += os.str();

    // save last event so we can compare it to next for double click
    m_last_button_event = event;


    if (event.button == *m_move_in_workspace_button ||
        event.button == *m_drag_to_workspace_button ) {
      
        m_move_window.curr_window = 0;
        m_move_window.client = ClientWindow(0);

        FbTk::FbWindow *win = 0;
        for (size_t w = 0; w < m_workspaces.size(); ++w) {
            win = m_workspaces[w]->find(event.subwindow);
            if (win && win->parent() != 0) {
                m_move_window.client = m_workspaces[w]->findClient(*win);
                m_move_window.curr_window = win;
                m_move_window.curr_workspace= m_workspaces[w];
                m_grab_x = event.x - win->x();
                m_grab_y = event.y - win->y();

                // drag -> move it above all workspaces
                if (event.button == *m_drag_to_workspace_button) {

                    m_move_window.curr_window->reparent(m_window, 
                                                        m_move_window.curr_window->x() + 
                                                        m_move_window.curr_workspace->window().x(), 
                                                        m_move_window.curr_window->y() + 
                                                        m_move_window.curr_workspace->window().y());
                }
                break;
            }
        }
    }

}

void FbPager::buttonReleaseEvent(XButtonEvent &event) {

    ClientWindow client(0);
    if (m_move_window.curr_window != 0 && 
        *m_move_in_workspace_button == event.button) {

        FbTk::FbWindow &win = *m_move_window.curr_window;
        int client_x = win.x(), client_y = win.y();
        scaleFromWindowToWindow(*(win.parent()), 
                                m_rootwin,
                                client_x, client_y);

        m_move_window.client.move(client_x, client_y);
        client = m_move_window.client;
        m_move_window.client = ClientWindow(0);
        m_move_window.curr_window = 0;

    }

    if (m_move_window.curr_window != 0 && *m_drag_to_workspace_button == event.button) {

        if (m_last_workspace_num >= 0 && 
            m_last_workspace_num < m_workspaces.size()) {
           
            Workspace* workspace = m_workspaces[m_last_workspace_num];

            // let someone else send it to the _new_ workspace
            if (workspace != m_move_window.curr_workspace) {
              
                ClientHandlerList::iterator it = m_clienthandlers.begin();
                ClientHandlerList::iterator it_end = m_clienthandlers.end();
                for (; it != it_end; ++it) {
                    (*it)->sendToWorkspace(m_move_window.client, m_last_workspace_num);
                }
            }

            // drop it to the last workspace, the mouse was over
            m_move_window.curr_window->reparent(workspace->window(), 
                                                m_move_window.curr_window->x() - 
                                                workspace->window().x(),
                                                m_move_window.curr_window->y() - 
                                                workspace->window().y());
                   
        }
        else {

            m_move_window.curr_window->reparent(m_move_window.curr_workspace->window(),
                                                m_grab_x, 
                                                m_grab_y);
        }
 
        // move it to the right position
        FbTk::FbWindow &win = *m_move_window.curr_window;
        int client_x = win.x(), client_y = win.y();
        scaleFromWindowToWindow(*(win.parent()), 
                                m_rootwin,
                                client_x, client_y);

        m_move_window.client.move(client_x, client_y);

        // clean up 
        m_move_window.client = ClientWindow(0);
        m_move_window.curr_window = 0;
        m_move_window.curr_workspace = 0;
        m_button_queue.erase();

    } else if (compareButtonQueues(m_button_queue, *m_change_workspace_button)) {
        // change workspace

        // find out which workspace
        Workspace *workspace = 0;
        FbTk::FbWindow *win = 0;
        size_t workspace_it = 0;
        for (; workspace_it < m_workspaces.size(); ++workspace_it) {
            if (m_workspaces[workspace_it]->window() == event.window) {
                workspace = m_workspaces[workspace_it];
                break;
            } else {
                win = m_workspaces[workspace_it]->find(event.window);
                if (win != 0) {
                    workspace = m_workspaces[workspace_it];
                    break;
                }
            }
        }
        
        if (workspace != 0)
            sendChangeToWorkspace(workspace_it);

    } else if (compareButtonQueues(m_button_queue, *m_raise_window_button) &&
               client.window() != 0) {
        // raise, currently the same as focus 

        for_each(m_clienthandlers.begin(),
                 m_clienthandlers.end(),
                 RefBind(&ClientHandler::setFocus, client));

        client.raise();
    } else if (compareButtonQueues(m_button_queue, *m_lower_window_button) &&
               client.window() != 0) {
        client.lower();
    } else if (compareButtonQueues(m_button_queue, *m_close_window_button) &&
               client.window() != 0) {
        for_each(m_clienthandlers.begin(),
                 m_clienthandlers.end(),
                 RefBind(&ClientHandler::closeWindow, client));
    } else if (compareButtonQueues(m_button_queue, *m_exit_button)) {
        // exit
        FbTk::App::instance()->end();
    } else if (compareButtonQueues(m_button_queue, *m_next_workspace_button)) {    
        // next workspace
        sendChangeToWorkspace(m_curr_workspace < m_workspaces.size() - 1 ? 
                              m_curr_workspace + 1 : 0);
    } else if (compareButtonQueues(m_button_queue, *m_prev_workspace_button)) {
        // prev workspace
        sendChangeToWorkspace(m_curr_workspace > 0 ? m_curr_workspace - 1 : 
                              m_workspaces.size() - 1);
    } else {
        // ok, we didn't find any queue that match 
        // so we return without clearing the current queue
        return; 
    }

    m_button_queue.erase();
}

void FbPager::motionNotifyEvent(XMotionEvent &event) {

    if (m_move_window.curr_window != 0 &&  
        m_move_window.curr_workspace != 0) {

        Workspace* workspace= 0;
        size_t w = 0;
        size_t old_workspace= m_last_workspace_num;
        int left, top, right, bottom, x, y;

        // find out, on which workspace the motion started and
        // on which workspace we are now
        for(; w < m_workspaces.size(); ++w) {
            
            if (m_workspaces[w]->window() == event.window)
                workspace= m_workspaces[w];

            left = m_workspaces[w]->window().x();
            top = m_workspaces[w]->window().y();
            right = left + m_workspaces[w]->window().width();
            bottom = top + m_workspaces[w]->window().height();

            // m_window - coordinate - space
            x = m_move_window.curr_workspace->window().x() + event.x;
            y = m_move_window.curr_workspace->window().y() + event.y;
               
            // inside the new workspace?
            if ( left <= x && x <= right && top <= y && y <= bottom ) 
                m_last_workspace_num= w;
        }
              
        int newx = event.x - m_grab_x;
        int newy = event.y - m_grab_y;

        // just move in current workspace
        if (m_last_button_event.button == *m_move_in_workspace_button) {

            respectConstraints(newx, newy, *m_move_window.curr_window, workspace->window());
            m_move_window.curr_window->move(newx, newy);

            if (*m_follow_move) {

                FbTk::FbWindow &win = *m_move_window.curr_window;
                int client_x = win.x(), client_y = win.y();
                scaleFromWindowToWindow(*(win.parent()), 
                                        m_rootwin,
                                        client_x, client_y);
                m_move_window.client.move(client_x, client_y);
            }
            
        // drag between the workspaces
        } else if (m_last_button_event.button == *m_drag_to_workspace_button) {

            newx += m_move_window.curr_workspace->window().x();
            newy += m_move_window.curr_workspace->window().y();
            
            respectConstraints(newx, newy, *m_move_window.curr_window, m_window);

            m_move_window.curr_window->move(newx, newy);

            if (*m_follow_drag && m_last_workspace_num != old_workspace)
                sendChangeToWorkspace(m_last_workspace_num);
        }
    }
}

void FbPager::exposeEvent(XExposeEvent &event) {
    if (m_window == event.window) {
        m_window.clearArea(event.x, event.y,
                           event.width, event.height);
    } else {
        for (size_t w = 0; w < m_workspaces.size(); ++w) {
            if (m_workspaces[w]->window() == event.window) {
                FbTk::FbWindow &win = m_workspaces[w]->window();
                win.clearArea(event.x, event.y,
                              event.width, event.height);
                return;
            } else {
                FbTk::FbWindow *win = m_workspaces[w]->find(event.window);
                if (win != 0) {
                    win->clearArea(event.x, event.y,
                                   event.width, event.height);
                    return;
                }
            }
        }
    }
}

void FbPager::clientMessageEvent(XClientMessageEvent &event) {

    ClientHandlerList::iterator it = m_clienthandlers.begin();
    ClientHandlerList::iterator it_end = m_clienthandlers.end();
    for (; it != it_end; ++it) {
        if ((*it)->clientMessage(*this, event))
            break;
    }

}

void FbPager::propertyEvent(XPropertyEvent &event) {
    updateWindowHints(event.window);
}

void FbPager::addWindow(Window win, unsigned int workspace_num) {
    if (workspace_num >= m_workspaces.size())
        return;

    m_windows.insert(win);

    m_workspaces[workspace_num]->add(win);
    // update window mode
    updateWindowHints(win);
    
}

void FbPager::moveToWorkspace(Window win, unsigned int w) {
    if (w >= 0x7FFFFFFF) {
        updateWindowHints(w);
        return;
    } else if (w >= m_workspaces.size())
        return;

    // remove from old workspace
    for (size_t workspace=0; workspace < m_workspaces.size(); ++workspace) {
        if (w != workspace && m_workspaces[workspace]->find(win) != 0)
            m_workspaces[workspace]->remove(win);
    }

    addWindow(win, w);
}

void FbPager::setFocusedWindow(Window win) {
    Workspace::setFocusedWindow(win);
    for_each(m_workspaces.begin(),
             m_workspaces.end(),
             mem_fun(&Workspace::updateFocusedWindow));
}

void FbPager::removeWindow(Window win) {
    for_each(m_workspaces.begin(),
             m_workspaces.end(),
             bind2nd(mem_fun(&Workspace::remove), win));
    FbTk::EventManager::instance()->remove(win);
    m_windows.erase(win);
}

void FbPager::updateWorkspaceCount(unsigned int num) {
    if (num == 0 || num == m_workspaces.size())
        return;

    if (m_workspaces.size() < num) {

        // add some workspaces until we match number of workspaces
        FbTk::Color wincolor(m_window_color_str->c_str(), m_window.screenNumber());
        FbTk::Color focusedcolor(m_focused_window_color_str->c_str(), m_window.screenNumber());
        FbTk::Color bordercol(m_window_bordercolor_str->c_str(), m_window.screenNumber());
        FbTk::Color backgroundcol(m_background_color_str->c_str(), m_window.screenNumber());

        while (m_workspaces.size() != num) {
            Workspace *w = new Workspace(*this, m_window, *m_workspace_width, *m_workspace_height,
                                         focusedcolor,
                                         wincolor,
                                         bordercol,
                                         backgroundcol,
                                         "workspace",
                                         *m_window_icons,
                                         *m_window_border_width);
            w->setAlpha(*m_alpha);
            m_workspaces.push_back(w);
        }
    } else { // remove some workspace until we match number or workspaces

        while (m_workspaces.size() != num) {
            delete m_workspaces.back();
            m_workspaces.pop_back();
            // extra precaution
            if (m_workspaces.empty())
                break;
        }
    }

    // update size of our main window and align workspaces
    alignWorkspaces();
    m_window.showSubwindows();
}

void FbPager::setCurrentWorkspace(unsigned int num) {
    if (num >= m_workspaces.size() || num == m_curr_workspace)
        return; 

    // set current workspaces background to "normal" background, 

    FbTk::Color workspace_background(m_background_color_str->c_str(), 
                                     m_window.screenNumber());
    m_workspaces[m_curr_workspace]->window().setBackgroundColor(workspace_background);
    m_workspaces[m_curr_workspace]->window().clear();

    // set next workspace background
    // update workspace background
    m_workspaces[num]->window().setBackgroundColor(FbTk::
                                                   Color(m_current_background_color_str->c_str(),
                                                         m_window.screenNumber()));
    m_workspaces[num]->window().clear();

    m_curr_workspace = num;
}

/**
   Setup workspaces position and size in our main window
*/
void FbPager::alignWorkspaces() {

    const int bevel = 1;
    int diff_x = 0;
    int diff_y = 0;

    // Fix alignment of workspaces
    if (*m_align == LEFT_TO_RIGHT)
        diff_x = *m_workspace_width + bevel;
    else // align TOP_TO_BOTTOM
        diff_y = *m_workspace_height + bevel;

    // resize and reposition all workspaces
    WorkspaceList::iterator it = m_workspaces.begin();
    WorkspaceList::iterator it_end = m_workspaces.end();
    int next_pos_x = bevel;
    int next_pos_y = bevel;
    int workspace = 0;
    int max_x = 0;
    int max_y = 0;
    for (; it != it_end; ++it, next_pos_y += diff_y, next_pos_x += diff_x, ++workspace) {
        if (workspace >= *m_workspaces_per_row) {            
            workspace = 0;
            if (*m_align == LEFT_TO_RIGHT) {
                next_pos_x = bevel;
                next_pos_y += *m_workspace_height + bevel;
            } else {
                next_pos_y = bevel;
                next_pos_x += *m_workspace_width + bevel;
            }
        }

        if (next_pos_x + *m_workspace_width > max_x)
            max_x = next_pos_x + *m_workspace_width;
        if (next_pos_y + *m_workspace_height > max_y)
            max_y = next_pos_y + *m_workspace_height;

        (*it)->window().move(next_pos_x, next_pos_y);
        (*it)->resize(*m_workspace_width, *m_workspace_height);
    }

    m_window.resize(max_x + bevel, max_y + bevel);

    XSizeHints sizehints;
    sizehints.max_width = sizehints.min_width = m_window.width();
    sizehints.max_height = sizehints.min_height = m_window.height();
    sizehints.flags = PMinSize | PMaxSize;
    XSetWMProperties(FbTk::App::instance()->display(), m_window.window(), 0, 0, 0, 0,
                     &sizehints, 0, 0);
    
    for_each(m_clienthandlers.begin(),
             m_clienthandlers.end(),
             RefBind(&ClientHandler::moveResize, m_window));
    
}


void FbPager::sendChangeToWorkspace(unsigned int num) {
    assert(num < m_workspaces.size());
    
    ClientHandlerList::iterator it = m_clienthandlers.begin();
    ClientHandlerList::iterator it_end = m_clienthandlers.end();
    for (; it != it_end; ++it)
        (*it)->changeWorkspace(m_window.screenNumber(), num);

}

void FbPager::updateWindowHints(Window win) {
    WindowHint hint;
    ClientWindow client(win);
    ClientHandlerList::iterator it = m_clienthandlers.begin();
    ClientHandlerList::iterator it_end = m_clienthandlers.end();
    for (; it != it_end; ++it)
        (*it)->getHints(client, hint);

    for (int workspace = 0; workspace < m_workspaces.size(); workspace++) {
        if (m_workspaces[workspace]->find(win) == 0) {
            // if the window is sticky then add it to this workspace too
            // and if it's not skip_pager nor type_dock
            if ((hint.flags() & WindowHint::WHINT_STICKY) &&
                !(hint.flags() & WindowHint::WHINT_SKIP_PAGER) &&
                !(hint.flags() & WindowHint::WHINT_TYPE_DOCK)) {
                cerr<<"STICKY!"<<endl;
                m_workspaces[workspace]->add(win);
            } else
                continue; // normal window without sticky

        } else if (!(hint.flags() & WindowHint::WHINT_STICKY) && 
                   hint.workspace() != workspace ||
                   (hint.flags() & WindowHint::WHINT_SKIP_PAGER) ||
                   (hint.flags() & WindowHint::WHINT_TYPE_DOCK)) {
            // if win not sticky and if it's not suppose to be on this
            // workspace then remove it and try next workspace
            m_workspaces[workspace]->remove(win);  
            continue; // next workspace
        }

        m_workspaces[workspace]->updateGeometry(win);

        if (hint.flags() & WindowHint::WHINT_SHADED) {
            m_workspaces[workspace]->shadeWindow(win);
        } else
            m_workspaces[workspace]->unshadeWindow(win);

        if ((hint.flags() & WindowHint::WHINT_ICONIC) && 
            !(hint.flags() & WindowHint::WHINT_SHADED))
            m_workspaces[workspace]->iconifyWindow(win);
        else if (!(hint.flags() & WindowHint::WHINT_SHADED))
            m_workspaces[workspace]->deiconifyWindow(win);
        
    } // end for
}

void FbPager::raiseWindow(Window win) {
    // raise on all workspace
    for_each(m_workspaces.begin(),
             m_workspaces.end(),
             bind2nd(mem_fun(&Workspace::raiseWindow), win));
}

void FbPager::lowerWindow(Window win) {
    // lower on all workspace
    for_each(m_workspaces.begin(),
             m_workspaces.end(),
             bind2nd(mem_fun(&Workspace::lowerWindow), win));
}

void FbPager::load(const std::string &filename) {
    if (!m_resmanager.load(FbTk::StringUtil::expandFilename(filename).c_str()))
        cerr<<"Failed to load resource file: "<<filename<<endl;

    m_window.setAlpha(*m_alpha);
    m_window.setBackgroundColor(FbTk::Color((*m_color_str).c_str(), m_window.screenNumber()));
    FbTk::Color bgcolor(m_background_color_str->c_str(), m_window.screenNumber());
    for (size_t workspace = 0; workspace < m_workspaces.size(); ++workspace) {
        m_workspaces[workspace]->setAlpha(*m_alpha);
        m_workspaces[workspace]->setWindowColor(*m_focused_window_color_str, 
                                                *m_window_color_str, 
                                                *m_window_bordercolor_str);
        m_workspaces[workspace]->window().setBackgroundColor(bgcolor);
    }

}

void FbPager::clearWindows() {
    m_window.clear();
    for_each(m_workspaces.begin(),
             m_workspaces.end(),
             mem_fun(&Workspace::clearWindows));
}

} // end namespace FbPager



syntax highlighted by Code2HTML, v. 0.9.1