// IconButton.cc
// Copyright (c) 2003 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org)
//                and Simon Bowden    (rathnor 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 "IconButton.hh"
#include "IconbarTool.hh"

#include "fluxbox.hh"
#include "Screen.hh"
#include "Window.hh"
#include "WinClient.hh"
#include "CommandParser.hh"

#include "FbTk/App.hh"
#include "FbTk/SimpleCommand.hh"
#include "FbTk/EventManager.hh"
#include "FbTk/MacroCommand.hh"
#include "FbTk/Command.hh"
#include "FbTk/RefCount.hh"
#include "FbTk/Menu.hh"

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

#include <X11/Xutil.h>
#ifdef SHAPE
#include <X11/extensions/shape.h>
#endif // SHAPE

typedef FbTk::RefCount<FbTk::Command> RefCmd;

namespace {

class ShowMenu: public FbTk::Command {
public:
    explicit ShowMenu(FluxboxWindow &win):m_win(win) { }
    void execute() {
        m_win.screen().hideMenus();
        // get last button pos
        const XEvent &event = Fluxbox::instance()->lastEvent();
        int x = event.xbutton.x_root - (m_win.menu().width() / 2);
        int y = event.xbutton.y_root - (m_win.menu().height() / 2);
        m_win.showMenu(x, y);
    }
private:
    FluxboxWindow &m_win;
};

class FocusCommand: public FbTk::Command {
public:
    explicit FocusCommand(const IconbarTool& tool, FluxboxWindow &win) : 
        m_win(win), m_tool(tool) { }
    void execute() {
        // this needs to be a local variable, as this object could be destroyed
        // if the workspace is changed.
        FluxboxWindow &win = m_win;
        if(win.isIconic() || !win.isFocused()) {
            switch(win.screen().getUserFollowModel()) {
            case BScreen::SEMIFOLLOW_ACTIVE_WINDOW:
                if (win.isIconic()) {
                    win.screen().sendToWorkspace(win.screen().currentWorkspaceID(), &win);
                } else {
                    win.screen().changeWorkspaceID(win.workspaceNumber());
                }
                break;
            case BScreen::FETCH_ACTIVE_WINDOW:
                win.screen().sendToWorkspace(win.screen().currentWorkspaceID(), &win);
                break;
            case BScreen::FOLLOW_ACTIVE_WINDOW:
                if (!win.isStuck())
                    win.screen().changeWorkspaceID(win.workspaceNumber());
            default:
                break;
            };
            win.raiseAndFocus();
       } else
           win.iconify();
    }

private:
    FluxboxWindow &m_win;
    const IconbarTool& m_tool;
};

// simple forwarding of wheeling, but only 
// if desktopwheeling is enabled
class WheelWorkspaceCmd : public FbTk::Command {
public:
    explicit WheelWorkspaceCmd(const IconbarTool& tool, FluxboxWindow &win, const char* cmd) : 
        m_win(win), m_cmd(CommandParser::instance().parseLine(cmd)), m_tool(tool) { }
    void execute() {

        switch(m_tool.wheelMode()) {
        case IconbarTool::ON:
            m_cmd->execute();
            break;
        case IconbarTool::SCREEN:
            if(m_win.screen().isDesktopWheeling())
                m_cmd->execute();
            break;
        case IconbarTool::OFF:
        default:
            break;
        };
    }

private:
    FluxboxWindow &m_win;
    RefCmd m_cmd;
    const IconbarTool& m_tool;
};

} // end anonymous namespace



IconButton::IconButton(const IconbarTool& tool, const FbTk::FbWindow &parent, 
                       FbTk::Font &font, FluxboxWindow &win):
    FbTk::TextButton(parent, font, win.winClient().title()),
    m_win(win), 
    m_icon_window(*this, 1, 1, 1, 1, 
                  ExposureMask | ButtonPressMask | ButtonReleaseMask),
    m_use_pixmap(true) {


    RefCmd next_workspace(new ::WheelWorkspaceCmd(tool, m_win, "nextworkspace"));
    RefCmd prev_workspace(new ::WheelWorkspaceCmd(tool, m_win, "prevworkspace"));

    //!! TODO: There're some issues with MacroCommand when
    //         this object dies when the last macrocommand is executed (focused cmd)
    //         In iconbar mode Icons
    //
    //    RefCmd hidemenus(new FbTk::SimpleCommand<BScreen>(win.screen(), &BScreen::hideMenus));
    //    FbTk::MacroCommand *focus_macro = new FbTk::MacroCommand();
    //    focus_macro->add(hidemenus);
    //    focus_macro->add(focus);
    
    RefCmd focus_cmd(new ::FocusCommand(tool, m_win));
    RefCmd menu_cmd(new ::ShowMenu(m_win));
    setOnClick(focus_cmd, 1);
    setOnClick(menu_cmd, 3);
    if(win.screen().isReverseWheeling()) {
        setOnClick(next_workspace, 5);
        setOnClick(prev_workspace, 4);
    } else {
        setOnClick(next_workspace, 4);
        setOnClick(prev_workspace, 5);
    }

    m_win.hintSig().attach(this);
    m_win.titleSig().attach(this);
    
    FbTk::EventManager::instance()->add(*this, m_icon_window);

    update(0);
}

IconButton::~IconButton() {
    // ~FbWindow cleans event manager
}


void IconButton::exposeEvent(XExposeEvent &event) {
    if (m_icon_window == event.window)
        m_icon_window.clear();
    else
        FbTk::TextButton::exposeEvent(event);
}

void IconButton::moveResize(int x, int y,
                            unsigned int width, unsigned int height) {

    FbTk::TextButton::moveResize(x, y, width, height);

    if (m_icon_window.width() != FbTk::Button::width() ||
        m_icon_window.height() != FbTk::Button::height()) {
        update(0); // update icon window
    }
}

void IconButton::resize(unsigned int width, unsigned int height) {
    FbTk::TextButton::resize(width, height);
    if (m_icon_window.width() != FbTk::Button::width() ||
        m_icon_window.height() != FbTk::Button::height()) {
        update(0); // update icon window
    }
}

void IconButton::clear() {
    setupWindow();
}

void IconButton::clearArea(int x, int y,
                           unsigned int width, unsigned int height,
                           bool exposure) {
    FbTk::TextButton::clearArea(x, y,
                                width, height, exposure);
}

void IconButton::setPixmap(bool use) {
    if (m_use_pixmap != use) {
        m_use_pixmap = use;
        update(0);
    }
}

void IconButton::update(FbTk::Subject *subj) {
    // we got signal that either title or 
    // icon pixmap was updated, 
    // so we refresh everything

    // we need to check our client first
    if (m_win.clientList().empty())
        return;

    Display *display = FbTk::App::instance()->display();

    int screen = m_win.screen().screenNumber();

    if (m_use_pixmap && m_win.usePixmap()) {
        // setup icon window
        m_icon_window.show();
        unsigned int w = width();
        unsigned int h = height();
        FbTk::translateSize(orientation(), w, h);
        int iconx = 1, icony = 1;
        unsigned int neww = w, newh = h;
        if (newh > 2*static_cast<unsigned>(icony))
            newh -= 2*icony;
        else
            newh = 1;
        neww = newh;

        FbTk::translateCoords(orientation(), iconx, icony, w, h);
        FbTk::translatePosition(orientation(), iconx, icony, neww, newh, 0);
        
        m_icon_window.moveResize(iconx, icony, neww, newh);

        m_icon_pixmap.copy(m_win.iconPixmap().drawable(), DefaultDepth(display, screen), screen);
        m_icon_pixmap.scale(m_icon_window.width(), m_icon_window.height());

        // rotate the icon or not?? lets go not for now, and see what they say...
        // need to rotate mask too if we do do this
        m_icon_pixmap.rotate(orientation());

        m_icon_window.setBackgroundPixmap(m_icon_pixmap.drawable());
    } else {
        // no icon pixmap
        m_icon_window.move(0, 0);
        m_icon_window.hide();
        m_icon_pixmap = 0;
    }

    if(m_use_pixmap && m_win.useMask()) {
        m_icon_mask.copy(m_win.iconMask().drawable(), 0, 0);
        m_icon_mask.scale(m_icon_pixmap.width(), m_icon_pixmap.height());
        m_icon_mask.rotate(orientation());
    } else
        m_icon_mask = 0;

#ifdef SHAPE

    XShapeCombineMask(display,
                      m_icon_window.drawable(),
                      ShapeBounding,
                      0, 0,
                      m_icon_mask.drawable(),
                      ShapeSet);

#endif // SHAPE

    if (subj == &(m_win.titleSig()))
        setText(m_win.title());

    if (subj != 0) {
        setupWindow();
    } else {
        m_icon_window.clear();
    }
}

void IconButton::setupWindow() {

    m_icon_window.clear();

    if (!m_win.clientList().empty()) {
        setText(m_win.winClient().title());
        // draw with x offset and y offset
    }
    FbTk::TextButton::clear();
}

void IconButton::drawText(int x, int y, FbTk::FbDrawable *drawable) {
    // offset text
    if (m_icon_pixmap.drawable() != 0)
        FbTk::TextButton::drawText(m_icon_window.x() + m_icon_window.width() + 1, y, drawable);
    else
        FbTk::TextButton::drawText(1, y, drawable);
}
                          
bool IconButton::setOrientation(FbTk::Orientation orient) {
    if (orientation() == orient)
        return true;

    if (FbTk::TextButton::setOrientation(orient)) {
        int iconx = 1, icony = 1;
        unsigned int tmpw = width(), tmph = height();
        FbTk::translateSize(orient, tmpw, tmph);
        FbTk::translateCoords(orient, iconx, icony, tmpw, tmph);
        FbTk::translatePosition(orient, iconx, icony, m_icon_window.width(), m_icon_window.height(), 0);
        m_icon_window.move(iconx, icony);
        return true;
    } else {
        return false;
    }
}