/* Menu.cc

Copyright © 2003 David Reveman.

This file is part of Waimea.

Waimea is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2, or (at your option) any later
version.

Waimea is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Waimea; see the file COPYING. If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */

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

extern "C" {
    
#ifdef    HAVE_STDIO_H
#  include <stdio.h>
#endif // HAVE_STDIO_H

#ifdef    STDC_HEADERS
#  include <string.h>
#endif // STDC_HEADERS

#ifdef    HAVE_UNISTD_H
#  include <sys/types.h>
#  include <sys/stat.h>
#  include <sys/wait.h>
#  include <unistd.h>
#endif // HAVE_UNISTD_H
    
}

#include "Menu.hh"

Menu::Menu(WaScreen *ws, char *n) :
    RootWindowObject(ws, 0, MenuType,
                     new WaStringMap(WindowIDName, n), "frame") {
    XSetWindowAttributes attrib_set;
    name = (*(ids->begin())).second;
    width = height = 0;
    x = y = 0;
    a_id = focus = (Window) 0;
    rootitem_id = rootitemlnk_id = (Window) 0;
    special_items = 0;
    special_menu_type = NoSpecialMenuType;
    width_factor = 1.0;

    if ((! ws->windowlist_menu) && (! strcmp(name, "windowlist"))) {
        ws->windowlist_menu = this;
        special_menu_type = WindowListMenuType;
    } else if (! strcmp(name, "clonemergelist")) {
        special_menu_type = CloneMergeListMenuType;
    } else if (! strcmp(name, "vertmergelist")) {
        special_menu_type = VertMergeListMenuType;
    } else if (! strcmp(name, "horizmergelist")) {
        special_menu_type = HorizMergeListMenuType;
    } else
        special_menu_type = NoSpecialMenuType;

    int create_mask = CWOverrideRedirect | CWEventMask | CWColormap |
        CWDontPropagate;

    if (ws->screen_depth == 32) {
        attrib_set.background_pixmap = None;
        attrib_set.background_pixel = 0x00000000;
        attrib_set.border_pixel = 0x00000000;
        create_mask |= CWBackPixel | CWBorderPixel;
    } else {
        attrib_set.background_pixmap = ParentRelative;
    }
    create_mask |= CWBackPixmap;
    
    attrib_set.colormap = ws->colormap;
    attrib_set.override_redirect = true;
    attrib_set.event_mask = NoEventMask;
    attrib_set.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask |
        ButtonMotionMask;
    
    id = XCreateWindow(ws->display, ws->id, x, y, 1, 1, 0,
                       ws->screen_depth, CopyFromParent, ws->visual,
                       create_mask, &attrib_set);
    ws->waimea->window_table.insert(make_pair(id, this));

    setStacking(ws->config.menu_stacking);
}

Menu::~Menu(void) {
    clear();
}

void Menu::applyAttributes(Parser *parser, Tst<char *> *attr) {
    char *value;
    value = parser->attrGetString(attr, "inherit", NULL);
    if (value) {
        Menu *inherit_menu = ws->getMenuNamed(value, false);
        if (inherit_menu == this) {
            parser->warning("menu %s cannot inherit itself\n", name);
        } else if (inherit_menu) {
            inheritAttributes(inherit_menu);
        } else {
            if (! parser->attrGetBool(attr, "ignore_missing", false))
                parser->warning("undefined menu: %s\n", value);
        }
    }
        
    width_factor = parser->attrGetDouble(attr, "width_factor", width_factor);
}

void Menu::clear(void) {
    while (! items.empty()) {
        items.back()->unref();
        items.pop_back();
    }
}

void Menu::inheritContent(Menu *inherit) {
    list<MenuItem *>::iterator it = inherit->items.begin();
    for (; it != inherit->items.end(); ++it) {
        items.push_back((MenuItem *) (*it)->ref());
    }
}

void Menu::inheritAttributes(Menu *inherit) {
    width_factor = inherit->width_factor;
}

void Menu::update(void) {
    unsigned int newwidth = 1;
    unsigned int newheight = 0;
    unsigned int calc_tmp;
    double _width, _top_spacing, _bottom_spacing,_left_spacing, _right_spacing;
    unsigned int top_spacing, bottom_spacing, left_spacing, right_spacing;

    style->fcalcWidth(ws->width, ws->hdpi, &_width);
    calc_length(style->top_spacing, style->top_spacing_u,
                ws->vdpi, ws->height, &_top_spacing);
    calc_length(style->bottom_spacing, style->bottom_spacing_u,
                ws->vdpi, ws->height, &_bottom_spacing);
    calc_length(style->left_spacing, style->left_spacing_u,
                ws->hdpi, ws->width, &_left_spacing);
    calc_length(style->right_spacing, style->right_spacing_u,
                ws->hdpi, ws->width, &_right_spacing);

    top_spacing = WA_ROUND_U(_top_spacing);
    bottom_spacing = WA_ROUND_U(_bottom_spacing);
    left_spacing = WA_ROUND_U(_left_spacing);
    right_spacing = WA_ROUND_U(_right_spacing);

    newheight = top_spacing;
    
    newwidth = WA_ROUND_U(_width * width_factor);

    list<MenuItem *>::iterator it = items.begin();
    for (; it != items.end(); ++it) {
        if ((*it)->icon_image)
            (*it)->setDynamicGroup(DynamicGroupMenuItemIconImageType,
                                   (*it)->icon_image);
        if ((*it)->icon_svg)
            (*it)->setDynamicGroup(DynamicGroupMenuItemIconSvgType,
                                   (*it)->icon_svg);
        (*it)->style->calcWidth(10, ws->hdpi, &calc_tmp);
        if (calc_tmp > newwidth) newwidth = calc_tmp;
        (*it)->newy = newheight;
        (*it)->newx = left_spacing;
        (*it)->style->calcHeight(10, ws->vdpi, &calc_tmp);
        newheight += calc_tmp;
    }
    
    it = items.begin();
    for (; it != items.end(); ++it) {
        (*it)->style->calcHeight(10, ws->vdpi, &calc_tmp);
        if ((*it)->width != newwidth ||
            (*it)->height != calc_tmp ||
            (*it)->newy != (*it)->y || (*it)->newx != (*it)->x) {
            (*it)->y = (*it)->newy;
            (*it)->x = (*it)->newx;
            (*it)->width = newwidth;
            (*it)->height = calc_tmp;
            XMoveResizeWindow(ws->display, (*it)->id, (*it)->x, (*it)->y,
                              ((*it)->width)? (*it)->width: 1,
                              ((*it)->height)? (*it)->height: 1);
        }
    }
    
    newwidth += left_spacing + right_spacing;
    newheight += bottom_spacing;

    if (newwidth != width || newheight != height) {
        width = newwidth;
        height = newheight;
        XResizeWindow(ws->display, id, (width)? width: 1, (height)? height: 1);
    }

    draw();
}

void Menu::draw(void) {
    pushRenderEvent();
    list<MenuItem *>::iterator it = items.begin();
    for (; it != items.end(); ++it)
        (*it)->pushRenderEvent();
}

void Menu::move(int newx, int newy) {
    int diff_x = newx - x;
    int diff_y = newy - y;
    if (newx != x || newy != y) {
        x = newx;
        y = newy;
        XMoveWindow(ws->display, id, x, y);
        pushRenderEvent();
        list<MenuItem *>::iterator it = items.begin();
        for (; it != items.end(); ++it) {
            (*it)->pushRenderEvent();
            if ((*it)->submenu &&
                (*it)->submenu->rootitem_id == (*it)->id) {
                (*it)->submenu->move((*it)->submenu->x + diff_x,
                                     (*it)->submenu->y + diff_y);
            }
        }
    }
}

void Menu::map(AWindowObject *_awo, bool focus, bool force) {
    Window w;
    int i, px, py;
    unsigned int ui;
    int workx, worky;
    unsigned int workw, workh;

    if ((! force) && mapped) return;
    if (ws->waimea->eh->move_resize != EndMoveResizeType) return;
    
    a_id = _awo->id;
    ws->getWorkareaSize(&workx, &worky, &workw, &workh);

    if (XQueryPointer(ws->display, ws->id, &w, &w, &px, &py, &i, &i, &ui)) {
        if ((py + (int) height) > ((int) workh + worky)) py -= height;
        if ((px + (int)width) > ((int) workw + workx)) px -= (width);
    }
    map(px, py);
    if (focus) focusFirst();
}

void Menu::map(int mapx, int mapy) {
    if (items.empty()) {
        ws->showWarningMessage("menu=%s does not contain any items.", name);
        return;
    }
    ws->raiseWindow(id);
    move(mapx, mapy);
    XUngrabPointer(ws->display, CurrentTime);
    if (! mapped) {
        WaWindow *ww = NULL;
        EventDetail ed;
        focus = (Window) 0;
        if (a_id) {
            AWindowObject *awo = (AWindowObject *)
                ws->waimea->findWin(a_id, ANY_ACTION_WINDOW_TYPE);
            if (awo) ww = awo->getWindow();
        }
        if (special_menu_type != NoSpecialMenuType) {
            switch (special_menu_type) {
                case WindowListMenuType:
                    addWindowListItems();
                    break;
                case CloneMergeListMenuType:
                case VertMergeListMenuType:
                case HorizMergeListMenuType:
                    if (ww) addMergeListItems(ww, special_menu_type);
                    break;
                default:
                    break;
            }
        }
        XMapWindow (ws->display, id);
        if (ww) {
            list<MenuItem *>::iterator it = items.begin();
            for (; it != items.end(); ++it) {
                ed.x = ed.y = INT_MAX;
                ed.x11mod = ed.wamod = 0;
                ed.type = MenuMapNotify;
                ws->waimea->eh->evAct(NULL, (*it)->id, &ed);
                ww->addMonitor(*it, (*it)->monitor_state);
                if (ww->wm_icon_image)
                    (*it)->setDynamicGroup(DynamicGroupWmIconImageType,
                                           ww->wm_icon_image);
                if (ww->wm_icon_svg)
                    (*it)->setDynamicGroup(DynamicGroupWmIconSvgType,
                                           ww->wm_icon_svg);
                (*it)->pushRenderEvent();
            }
        }
        if (rootitemlnk_id) {
            ed.x = ed.y = INT_MAX;
            ed.x11mod = ed.wamod = 0;
            ed.type = SubmenuMapNotify;
            ws->waimea->eh->evAct(NULL, rootitemlnk_id, &ed);
        }
    }
    mapped = true;
}

void Menu::unmap(void) {
    bool was_mapped = mapped;
    unmapSubmenus(NULL);

    mapped = false;

    list<MenuItem *>::iterator it = items.begin();
    for (; it != items.end(); ++it) {
        if ((*it)->focused && (*it)->id == ws->waimea->prefocus) {
            ws->waimea->focusRevertFrom(ws, (*it)->id);
            break;
        }
    }
    rootitem_id = (Window) 0;
    
    if (was_mapped) {
        EventDetail ed;
        WaWindow *ww = NULL;
        XUnmapWindow(ws->display, id);
        if (a_id) {
            AWindowObject *awo = (AWindowObject *)
                ws->waimea->findWin(a_id, WindowType);
            if (awo) ww = awo->getWindow();
        }
        if (special_items) removeSpecialItems();
        it = items.begin();
        for (; it != items.end(); ++it) {
            ed.x = ed.y = INT_MAX;
            ed.x11mod = ed.wamod = 0;
            ed.type = MenuUnmapNotify;
            ws->waimea->eh->evAct(NULL, (*it)->id, &ed);
            if (ww) ww->removeMonitor(*it);
        }
        if (rootitemlnk_id) {
            ed.x = ed.y = INT_MAX;
            ed.x11mod = ed.wamod = 0;
            ed.type = SubmenuUnmapNotify;
            ws->waimea->eh->evAct(NULL, rootitemlnk_id, &ed);
        }
    }
    a_id = (Window) 0;
}

void Menu::unLink(void) {
    rootitem_id = (Window) 0;
}

void Menu::unmapSubmenus(MenuItem *mi) {
    list<MenuItem *>::iterator it = items.begin();
    for (; it != items.end(); ++it)
        if (mi != *it) {
            if ((*it)->submenu &&
                (*it)->submenu->rootitem_id == (*it)->id)
                (*it)->submenu->unmap();
        }
}

void Menu::raiseSubmenus(void) {
    list<MenuItem *>::iterator it = items.begin();
    for (; it != items.end(); ++it) {
        if ((*it)->submenu &&
            (*it)->submenu->rootitem_id == (*it)->id) {
            ws->raiseWindow((*it)->submenu->id, false);
            (*it)->submenu->raiseSubmenus();
        }
    }
}

void Menu::lowerSubmenus(void) {
    list<MenuItem *>::iterator it = items.begin();
    for (; it != items.end(); ++it) {
        if ((*it)->submenu &&
            (*it)->submenu->rootitem_id == (*it)->id) {
            (*it)->submenu->lowerSubmenus();
            ws->lowerWindow((*it)->submenu->id, false);
        }
    }
}


void Menu::unmapTree(void) {
    if (rootitem_id) {
        MenuItem *rootitem = (MenuItem *)
            ws->waimea->findWin(rootitem_id, MenuItemType);
        if (rootitem) rootitem->menu->unmapTree();
        else unmap();
    }
    else unmap();
}

void Menu::focusFirst(void) {
    if (! mapped) return;
    list<MenuItem *>::iterator it = items.begin();
    for (; it != items.end(); ++it)
        if ((*it)->style->focusable) {
            ws->waimea->focusNew((*it)->id);
            break;
        }
}

void Menu::drawOutline(int ox, int oy) {
    if (outline_state) clearOutline();
    outline_state = true;

    int diff_x = ox - x;
    int diff_y = oy - y;
    outl_x = ox;
    outl_y = oy;

    XDrawRectangle(ws->display, ws->id, ws->xor_gc,
                   outl_x, outl_y, width, height);

    list<MenuItem *>::iterator it = items.begin();
    for (; it != items.end(); ++it) {
        if ((*it)->submenu &&
            (*it)->submenu->rootitem_id == (*it)->id) {
            (*it)->submenu->drawOutline((*it)->submenu->x + diff_x,
                                        (*it)->submenu->y + diff_y);
        }
    }
}

void Menu::clearOutline(void) {
    if (! outline_state) return;
    outline_state = false;

    XDrawRectangle(ws->display, ws->id, ws->xor_gc,
                   outl_x, outl_y, width, height);
    
    list<MenuItem *>::iterator it = items.begin();
    for (; it != items.end(); ++it) {
        if ((*it)->submenu &&
            (*it)->submenu->rootitem_id == (*it)->id) {
            (*it)->submenu->clearOutline();
        }
    }
}

void Menu::removeSpecialItems(void) {
    while (special_items) {
        items.back()->unref();
        items.pop_back();
        special_items--;
    }
}

void Menu::addWindowListItems(void) {
    list<WaWindow *>::iterator it = ws->wawindow_list.begin();
    for (; it != ws->wawindow_list.end() &&
             (! ((*it)->wstate & StateTasklistMask)); ++it);
    
    if (it == ws->wawindow_list.end()) return;

    WaWindow *next = NULL, *last = *it;
    while (next != last) {
        it++;
        if (it == ws->wawindow_list.end())
            next = last;
        else
            next = *it;
        if (! (next->wstate & StateTasklistMask)) continue;
        MenuItem *mi = new MenuItem(ws, this, "item");
        mi->commonStyleUpdate();
        delete [] mi->str;
        mi->str = WA_STRDUP(next->name);
        MenuItemAction *mia = new MenuItemAction();
        mia->func = &AWindowObject::windowRaiseFocus;
        mi->actions.push_back(mia);
        
        mi->a_id = next->id;
        
        if (next->wm_icon_image)
            mi->setDynamicGroup(DynamicGroupWmIconImageType,
                                next->wm_icon_image);

        if (next->wm_icon_svg)
            mi->setDynamicGroup(DynamicGroupWmIconSvgType,
                                next->wm_icon_svg);
        
        items.push_back(mi);
        special_items++;
    }
    update();
}

void Menu::addMergeListItems(WaWindow *ww, SpecialMenuType smtype) {
    list<WaWindow *>::iterator it = ws->wawindow_list.begin();
    for (; it != ws->wawindow_list.end() &&
             (! ((*it)->wstate & StateTasklistMask)) &&
             (*it)->id != ww->id && (! (*it)->master); ++it);
    
    if (it == ws->wawindow_list.end()) return;

    WaWindow *next = NULL, *last = *it;
    while (next != last) {
        it++;
        if (it == ws->wawindow_list.end())
            next = last;
        else
            next = *it;
        if (! (next->functions & FunctionMergeMask)) continue;
        if (next->id == ww->id) continue;
        if (next->master) continue;

        MenuItem *mi;
        switch (smtype) {
            case CloneMergeListMenuType:
                mi = new MenuItem(ws, this, "item");
                break;
            case VertMergeListMenuType:
                mi = new MenuItem(ws, this, "item");
                break;
            default:
                mi = new MenuItem(ws, this, "item");
        }
        mi->commonStyleUpdate();
        delete [] mi->str;
        mi->str = WA_STRDUP(next->name);
        mi->a_id = next->id;

        if (next->wm_icon_image)
            mi->setDynamicGroup(DynamicGroupWmIconImageType,
                                next->wm_icon_image);

        if (next->wm_icon_svg)
            mi->setDynamicGroup(DynamicGroupWmIconSvgType,
                                next->wm_icon_svg);
        
        items.push_back(mi);
        special_items++;
    }
    update();
}

void Menu::styleUpdate(bool, bool) {
    update();
}

MenuItemAction::MenuItemAction(void) {
    func = NULL;
    param = NULL;
}

MenuItemAction::~MenuItemAction(void) {
    if (param) delete [] param;
}

bool MenuItemAction::applyAttributes(Parser *parser, Tst<char *> *attr) {
    char *value = parser->attrGetString(attr, "function", NULL);
    if (value) {
        Tst<ActionFunc>::iterator it = actionfunction_tst->find(value);
        if (it != actionfunction_tst->end()) func = *it;
        
        if (! func) {
            parser->warning("unknown action function `%s'\n", value);
            return false;
        }
    } else
        return false;
    
    value = parser->attrGetString(attr, "parameter", NULL);
    if (value) {
        if (param) delete [] param;
        param = WA_STRDUP(value);
    }

    return true;
}

MenuItem::MenuItem(WaScreen *_ws, Menu *m, char *n) :
    DWindowObject(_ws, 0, MenuItemType, m->ids->ref(), n) {
    XSetWindowAttributes attrib_set;
    str = WA_STRDUP("");
    str2 = WA_STRDUP("");
    str_dynamic = str2_dynamic = false;
    submenuname = NULL;
    menu = (Menu *) m->ref();
    focused = mstate = hilited = false;
    submenu = NULL;
    height = width = 0;
    x = y = -1;
    monitor_state = 0;
    a_id = (Window) 0;
    icon_image = NULL;
    icon_svg = NULL;

    int create_mask = CWOverrideRedirect | CWEventMask;

    attrib_set.override_redirect = true;
    attrib_set.event_mask = ButtonPressMask | ButtonReleaseMask |
        EnterWindowMask | LeaveWindowMask | KeyPressMask |
        KeyReleaseMask | FocusChangeMask;
    id = XCreateWindow(ws->display, menu->id, 0, 0, 1, 1, 0,
                       CopyFromParent, CopyFromParent, CopyFromParent,
                       create_mask, &attrib_set);
    ws->waimea->window_table.insert(make_pair(id, this));
    XMapWindow(ws->display, id);

    hidden = false;
    decor_root = menu;
    
    resetStyle();
}

MenuItem::~MenuItem(void) {
    LISTDEL(actions);
    delete [] str;
    delete [] str2;
    if (submenuname) delete [] submenuname;
    if (icon_image) icon_image->unref();
    if (icon_svg) icon_svg->unref();
    ws->waimea->window_table.erase(id);
    ws->waimea->removeFromFocusHistory(id);

    destroySubwindows();
    XDestroyWindow(ws->display, id);
    menu->unref();
}

void MenuItem::applyAttributes(Parser *parser, Tst<char *> *attr) {
    MenuItemAction *mia = new MenuItemAction();
    if (mia->applyAttributes(parser, attr))
        actions.push_back(mia);
    else
        delete mia;    

    char *value = parser->attrGetString(attr, "string", NULL);
    if (value) {
        delete [] str;
        str = preexpand(value, &str_dynamic);
    }
    value = parser->attrGetString(attr, "string2", NULL);
    if (value) {
        delete [] str2;
        str2 = preexpand(value, &str2_dynamic);
    }
    value = parser->attrGetString(attr, "image", NULL);
    if (value) {
        int image_width, image_height;
        unsigned char *rgba = NULL;
        char *filename = smartfile(value, parser->filename);
        if (filename) {
            rgba = read_image_to_rgba(filename, &image_width, &image_height);
            if (rgba) {
                WaSurface *image = ws->rgbaToWaSurface(rgba, image_width,
                                                       image_height);
                if (image) {
                    icon_image = new RenderGroup(ws, NULL);
                    RenderOpImage *image_op = new RenderOpImage();
                    image_op->image = image;
                    image_op->scale = ImageNormalScaleType;
                    icon_image->operations.push_back(image_op);
                    icon_image->_w = image_width;
                    icon_image->_h = image_height;
                    icon_image->_x = icon_image->_y = 0;
                    icon_image->_xu = icon_image->_yu = icon_image->_wu =
                        icon_image->_hu = PXLenghtUnitType;
                }
            }
            delete [] filename;
        }
        
        if (! icon_image)
            parser->warning("failed loading image=%s\n", value);
    }

#ifdef    SVG
    value = parser->attrGetString(attr, "svg", NULL);
    if (value) {
        char *filename = smartfile(value, parser->filename);
        if (filename) {
            FILE *file = fopen(filename, "r");
            if (file) {
                svg_cairo_t *cairo_svg;
                svg_cairo_status status;
                status = svg_cairo_create(&cairo_svg);
                if (status == SVG_CAIRO_STATUS_SUCCESS) {
                    status = svg_cairo_parse_file(cairo_svg, file);
                    if (status == SVG_CAIRO_STATUS_SUCCESS) {
                        RenderOpSvg *svg_op = new RenderOpSvg();
                        svg_op->cairo_svg = cairo_svg; 
                        icon_svg = new RenderGroup(ws, NULL);
                        icon_svg->operations.push_back(svg_op);
                        icon_svg->_w = 16;
                        icon_svg->_h = 16;
                        icon_svg->_x = icon_svg->_y = 0;
                        icon_svg->_xu = icon_svg->_yu = icon_svg->_wu =
                            icon_svg->_hu = PXLenghtUnitType;
                    } else {
                        parser->warning("parsing of svg file %s failed\n",
                                        value);
                    }
                } else {
                    parser->warning("creation of svg context failed\n");
                }
                fclose(file);
            }
            delete [] filename;
        } else
            parser->warning("unable to open svg file: %s\n", value);
    }
#endif // SVG
    
    value = parser->attrGetString(attr, "submenu", NULL);
    if (value) {
        submenu = ws->getMenuNamed(submenuname, false);
        if (! submenu) submenuname = WA_STRDUP(value);
    }
    
    value = parser->attrGetString(attr, "monitor_window_state", NULL);
    if (value) {
        Tst<long int>::iterator it = client_window_state_tst->find(value);
        if (it != client_window_state_tst->end())
            monitor_state = *it;
        else
            parser->warning("unknown window state=%s\n", value);
    }
}

void MenuItem::mapSubmenu(char *param, bool force) {
    int workx, worky;
    unsigned int workw, workh;
    int xoff = 0;
    int yoff = 0;
    
    if (! submenu) {
        submenu = ws->getMenuNamed(submenuname, true);
        if (! submenu) return;
    }
    
    if ((! force) && submenu->mapped) return;
    
    ws->getWorkareaSize(&workx, &worky, &workw, &workh);

    if (param) {
        double value, value_return;
        LenghtUnitType unit;
        char *yoff_str = strchr(param, ',');
        if (yoff_str) {
            value = get_double_and_unit(yoff_str + 1, &unit);
            calc_length(value, unit, ws->vdpi, menu->height, &value_return);
            yoff = WA_ROUND(value_return);
            *yoff_str = '\0';
        }
        value = get_double_and_unit(param, &unit);
        calc_length(value, unit, ws->hdpi, menu->width, &value_return);
        xoff = WA_ROUND(value_return);
        if (yoff_str) *yoff_str = ',';
        if (xoff > (int) menu->width) xoff = menu->width;
        if (xoff < - (int) menu->width) xoff = - (int) menu->width;
        if (yoff > (int) menu->height) yoff = menu->height;
        if (yoff < - (int) menu->height) yoff = - (int) menu->height;
    }
    
    int mapx = menu->x + menu->width + xoff;
    int mapy = menu->y + y;

    list<MenuItem *>::iterator it = submenu->items.begin();
    for (; it != submenu->items.end(); ++it)
        if ((*it)->style->focusable) {
            mapy -= (*it)->y;
            break;
        }

    mapy += yoff;
    
    int diff = (mapy + submenu->height) - (workh + worky);
    if (diff > 0) mapy -= diff;
    if (mapy < 0) mapy = 0;
    if ((mapx + (int) submenu->width) > ((int) workw + workx))
        mapx = menu->x - submenu->width - xoff;

    submenu->rootitem_id = submenu->rootitemlnk_id = id;
    submenu->a_id = menu->a_id;
    submenu->map(mapx, mapy);
    if (focused) submenu->focusFirst();
}

void MenuItem::perform(XEvent *e) {
    AWindowObject *awo = NULL;
    if (a_id) {
        awo = (AWindowObject *) ws->waimea->findWin(a_id,
                                                    ANY_ACTION_WINDOW_TYPE);
        if (! awo) return;
    } else if (menu->a_id) {
        awo = (AWindowObject *) ws->waimea->findWin(menu->a_id,
                                                    ANY_ACTION_WINDOW_TYPE);
        if (! awo) return;
    }
    
    Action *action = new Action;
    action->param = NULL;

    if (! actions.empty()) {
        list<MenuItemAction *>::iterator it = actions.begin();
        for (; it != actions.end(); it++) {
            if (action->param) delete [] action->param;
            if ((*it)->param) action->param = WA_STRDUP((*it)->param);
            else action->param = NULL;

            if (awo)
                ((*(awo)).*((*it)->func))(e, action);
            else
                ((*ws).*((*it)->func))(e, action);
        }
    }
    else if (menu->special_menu_type != NoSpecialMenuType &&
             awo && awo->type == WindowType) {
        WaWindow *master = (WaWindow *) awo;
        WaWindow *child = NULL;
        AWindowObject *m_awo = (AWindowObject *)
            ws->waimea->findWin(menu->a_id, ANY_ACTION_WINDOW_TYPE);
        if (m_awo) child = m_awo->getWindow();
        if (child) {
            switch (menu->special_menu_type) {
                case CloneMergeListMenuType:
                    master->merge(child, CloneMergeType);
                    break;
                case VertMergeListMenuType:
                    master->merge(child, VertMergeType);
                    break;
                case HorizMergeListMenuType:
                    master->merge(child, HorizMergeType);
                    break;
                default:
                    break;
            }
        }
    }
    action->unref();
}

void MenuItem::nextItem(void) {
    MenuItem *first = NULL;
    MenuItem *next = NULL;
    bool find_next = false;
    list<MenuItem *>::iterator it = menu->items.begin();
    for (; it != menu->items.end(); ++it) {
        if (*it == this) find_next = true;
        else if ((! find_next) && (! first) && (*it)->style->focusable)
            first = *it;
        else if (find_next && (*it)->style->focusable) {
            next = *it;
            break;
        }
    }
    if (next) ws->waimea->focusNew(next->id);
    else if (first) ws->waimea->focusNew(first->id);
}


void MenuItem::previousItem(void) {
    MenuItem *last = NULL;
    MenuItem *prev = NULL;
    bool find_prev = true;
    list<MenuItem *>::iterator it = menu->items.begin();
    for (; it != menu->items.end(); ++it) {
        if (*it == this) find_prev = false;
        else if (find_prev && (*it)->style->focusable) prev = *it;
        else if ((*it)->style->focusable) last = *it;
    }
    if (prev) ws->waimea->focusNew(prev->id);
    else if (last) ws->waimea->focusNew(last->id);
}

void MenuItem::move(void) {
    XEvent event, *map_ev;
    int px, py, i;
    list<XEvent *> *maprequest_list;
    int nx = menu->x;
    int ny = menu->y;
    Window w;
    unsigned int ui;

    if (ws->waimea->eh->move_resize != EndMoveResizeType) return;
    ws->waimea->eh->move_resize = MoveType;
    
    XQueryPointer(ws->display, ws->id, &w, &w, &px, &py, &i, &i, &ui);
    
    maprequest_list = new list<XEvent *>;
    if (XGrabPointer(ws->display, id, true, ButtonReleaseMask |
                     ButtonPressMask | PointerMotionMask | EnterWindowMask |
                     LeaveWindowMask, GrabModeAsync, GrabModeAsync,
                     menu->ws->id, None, CurrentTime) != GrabSuccess) {
        ws->waimea->eh->move_resize = EndMoveResizeType;
        delete maprequest_list;
        return;
    }
    if (XGrabKeyboard(ws->display, id, true, GrabModeAsync, GrabModeAsync,
                      CurrentTime) != GrabSuccess) {
        ws->waimea->eh->move_resize = EndMoveResizeType;
        delete maprequest_list;
        return;
    }
    for (;;) {
        ws->waimea->eh->eventLoop(
            &ws->waimea->eh->menu_viewport_move_return_mask, &event);
        switch (event.type) {
            case MotionNotify:
                while (XCheckTypedWindowEvent(ws->display,
                                              event.xmotion.window,
                                              MotionNotify, &event));
                nx += event.xmotion.x_root - px;
                ny += event.xmotion.y_root - py;
                px = event.xmotion.x_root;
                py = event.xmotion.y_root;
                menu->drawOutline(nx, ny);
                break;
            case LeaveNotify:
            case EnterNotify:
                if (ws->west->id == event.xcrossing.window ||
                    ws->east->id == event.xcrossing.window ||
                    ws->north->id == event.xcrossing.window ||
                    ws->south->id == event.xcrossing.window) {
                    menu->clearOutline();
                    ws->waimea->eh->handleEvent(&event);
                    menu->drawOutline(nx, ny);
                } else if (event.type == EnterNotify &&
                           event.xany.window != id) {
                    int cx, cy;
                    XQueryPointer(ws->display, ws->id, &w, &w, &cx,
                                  &cy, &i, &i, &ui);
                    nx += cx - px;
                    ny += cy - py;
                    px = cx;
                    py = cy;
                    menu->drawOutline(nx, ny);
                }
                break;
            case MapRequest:
                map_ev = new XEvent;
                *map_ev = event;
                maprequest_list->push_front(map_ev); break;
            case ButtonPress:
            case ButtonRelease:
                event.xbutton.window = id;
            case KeyPress:
            case KeyRelease:
                if (event.type == KeyPress || event.type == KeyRelease)
                    event.xkey.window = id;
                menu->clearOutline();
                ws->waimea->eh->handleEvent(&event);
                if (ws->waimea->eh->move_resize != EndMoveResizeType) {
                    menu->drawOutline(nx, ny);
                    break;
                }
                menu->move(nx, ny);
                while (! maprequest_list->empty()) {
                    XPutBackEvent(ws->display, maprequest_list->front());
                    delete maprequest_list->front();
                    maprequest_list->pop_front();
                }
                delete maprequest_list;
                XUngrabKeyboard(ws->display, CurrentTime);
                XUngrabPointer(ws->display, CurrentTime);
                return;
        }
    }
}

void MenuItem::moveOpaque(void) {
    XEvent event, *map_ev;
    int px, py, i;
    list<XEvent *> *maprequest_list;
    int nx = menu->x;
    int ny = menu->y;
    Window w;
    unsigned int ui;

    if (ws->waimea->eh->move_resize != EndMoveResizeType) return;
    ws->waimea->eh->move_resize = MoveType;
    
    XQueryPointer(ws->display, ws->id, &w, &w, &px, &py, &i, &i, &ui);
    
    maprequest_list = new list<XEvent *>;
    if (XGrabPointer(ws->display, id, true, ButtonReleaseMask |
                     ButtonPressMask | PointerMotionMask | EnterWindowMask |
                     LeaveWindowMask, GrabModeAsync, GrabModeAsync,
                     menu->ws->id, None, CurrentTime) != GrabSuccess) {
        ws->waimea->eh->move_resize = EndMoveResizeType;
        delete maprequest_list;
        return;
    }
    if (XGrabKeyboard(ws->display, id, true, GrabModeAsync, GrabModeAsync,
                      CurrentTime) != GrabSuccess) {
        ws->waimea->eh->move_resize = EndMoveResizeType;
        delete maprequest_list;
        return;
    }
    for (;;) {
        ws->waimea->eh->eventLoop(
            &ws->waimea->eh->menu_viewport_move_return_mask, &event);
        switch (event.type) {
            case MotionNotify:
                while (XCheckTypedWindowEvent(ws->display,
                                              event.xmotion.window,
                                              MotionNotify, &event));
                nx += event.xmotion.x_root - px;
                ny += event.xmotion.y_root - py;
                px = event.xmotion.x_root;
                py = event.xmotion.y_root;
                menu->move(nx, ny);
                break;
            case LeaveNotify:
            case EnterNotify:
                if (ws->west->id == event.xcrossing.window ||
                    ws->east->id == event.xcrossing.window ||
                    ws->north->id == event.xcrossing.window ||
                    ws->south->id == event.xcrossing.window) {
                    ws->waimea->eh->handleEvent(&event);
                } else if (event.type == EnterNotify &&
                           event.xany.window != id) {
                    int cx, cy;
                    XQueryPointer(ws->display, ws->id, &w, &w, &cx,
                                  &cy, &i, &i, &ui);
                    nx += cx - px;
                    ny += cy - py;
                    px = cx;
                    py = cy;
                    menu->move(nx, ny);
                }
                break;
            case MapRequest:
                map_ev = new XEvent;
                *map_ev = event;
                maprequest_list->push_front(map_ev); break;
            case ButtonPress:
            case ButtonRelease:
                event.xbutton.window = id;
            case KeyPress:
            case KeyRelease:
                if (event.type == KeyPress || event.type == KeyRelease)
                    event.xkey.window = id;
                ws->waimea->eh->handleEvent(&event);
                if (ws->waimea->eh->move_resize != EndMoveResizeType) break;
                while (! maprequest_list->empty()) {
                    XPutBackEvent(ws->display, maprequest_list->front());
                    delete maprequest_list->front();
                    maprequest_list->pop_front();
                }
                delete maprequest_list;
                XUngrabKeyboard(ws->display, CurrentTime);
                XUngrabPointer(ws->display, CurrentTime);
                return;
        }
    }
}

void MenuItem::currentPositionAndSize(int *ret_x, int *ret_y,
                                      unsigned int *ret_w,
                                      unsigned int *ret_h) {
    *ret_x = x;
    *ret_y = y;
    *ret_w = width;
    *ret_h = height;
}

void MenuItem::evalWhatToRender(bool, bool size_change,
                                bool *render_texture,
                                bool *render_alpha,
                                bool *update_shape) {
    
    if ((! size_change) &&
        (sb->x > (int) ws->width || sb->y > (int) ws->height ||
         sb->x + (int) sb->width < 0 || sb->y + (int) sb->height < 0)) {
        *render_texture = false;
        *render_alpha = false;
        *update_shape = false;
        return;
    }

    Pixmap newbg = (menu->sb->surface)? menu->sb->surface->pixmap: None;

    if (sb->bg != newbg) {
        *render_alpha = true;
        sb->bg = newbg;
    }
}

WaSurface *MenuItem::getBgInfo(DWindowObject **return_dwo,
                               int *return_x, int *return_y) {
    *return_x = x;
    *return_y = y;
    *return_dwo = menu;
    return menu->sb->surface;
}


syntax highlighted by Code2HTML, v. 0.9.1