// Menu.hh for FbTk - Fluxbox Toolkit
// Copyright (c) 2001 - 2004 Henrik Kinnunen (fluxgen at fluxbox dot org)
//
// Basemenu.hh for Blackbox - an X11 Window manager
// Copyright (c) 1997 - 2000 Brad Hughes (bhughes at tcac.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.

#ifndef FBTK_MENU_HH
#define FBTK_MENU_HH

#include <vector>
#include <memory>

#include "FbString.hh"
#include "FbWindow.hh"
#include "EventHandler.hh"
#include "Observer.hh"
#include "MenuTheme.hh"
#include "Timer.hh"
#include "TypeAhead.hh"

namespace FbTk {

class Command<class T>;
class MenuItem;
class ImageControl;
class RefCount<class T>;

///   Base class for menus
class Menu: public FbTk::EventHandler, FbTk::FbWindowRenderer,
            public FbTk::Observer {
public:
    enum Alignment{ ALIGNDONTCARE = 1, ALIGNTOP, ALIGNBOTTOM };
    enum { RIGHT = 1, LEFT };

    /**
       Bullet type
    */
    enum { EMPTY = 0, SQUARE, TRIANGLE, DIAMOND };

    Menu(FbTk::ThemeProxy<MenuTheme> &tm, ImageControl &imgctrl);
    virtual ~Menu();

    /**
       @name manipulators
    */
    //@{
    /// add a menu item with a label and a command
    int insert(const FbString &label, RefCount<Command<void> > &cmd, int pos=-1);
    /// add empty menu item
    int insert(const FbString &label, int pos=-1);
    /// add submenu
    int insert(const FbString &label, Menu *submenu, int pos= -1);
    /// add menu item
    int insert(MenuItem *item, int pos=-1);
    /// remove an item
    int remove(unsigned int item);
    /// remove all items
    void removeAll();
    void setInternalMenu(bool val = true) { m_internal_menu = val; }
    void setAlignment(Alignment a) { m_alignment = a; }
#ifdef NOT_USED
    void setTorn() { m_torn = true; }
    void removeParent() { if (m_internal_menu) m_parent = 0; }
#endif
    /// raise this window
    virtual void raise();
    /// lower this window
    virtual void lower();
    /// cycle through menuitems
    void cycleItems(bool reverse);
    void enterSubmenu();

    void disableTitle();
    void enableTitle();

    void setScreen(int x, int y, int w, int h);

    /**
       @name event handlers
    */
    //@{
    void handleEvent(XEvent &event);
    void buttonPressEvent(XButtonEvent &bp);
    virtual void buttonReleaseEvent(XButtonEvent &br);
    void motionNotifyEvent(XMotionEvent &mn);
    void exposeEvent(XExposeEvent &ee);
    void keyPressEvent(XKeyEvent &ke);
    void leaveNotifyEvent(XCrossingEvent &ce);
    //@}
    /// get input focus
    void grabInputFocus();
    virtual void reconfigure();
    /// set label string
    void setLabel(const FbString &labelstr);
    /// move menu to x,y
    virtual void move(int x, int y);
    virtual void updateMenu(int active_index = -1);
    void setItemSelected(unsigned int index, bool val);
    void setItemEnabled(unsigned int index, bool val);
    void setMinimumSublevels(int m) { menu.minsub = m; }
    virtual void drawSubmenu(unsigned int index);
    /// show menu
    virtual void show();
    /// hide menu
    virtual void hide(bool force = false);
    virtual void clearWindow();
#ifdef NOT_USED
    void setActiveIndex(int index) { m_active_index = index; }
    /*@}*/

    /**
       @name accessors
    */
    //@{
    int activeIndex() const { return m_active_index; }
#endif
    bool isTorn() const { return m_torn; }
    bool isVisible() const { return m_visible; }
    bool isMoving() const { return m_moving; }
    int screenNumber() const { return menu.window.screenNumber(); }
    Window window() const { return menu.window.window(); }
    FbWindow &fbwindow() { return menu.window; }
    const FbWindow &fbwindow() const { return menu.window; }
    FbWindow &titleWindow() { return menu.title; }
    FbWindow &frameWindow() { return menu.frame; }
    const std::string &label() const { return menu.label; }
    int x() const { return menu.window.x(); }
    int y() const { return menu.window.y(); }
    unsigned int width() const { return menu.window.width(); }
    unsigned int height() const { return menu.window.height(); }
    size_t numberOfItems() const { return menuitems.size(); }
    int currentSubmenu() const { return m_which_sub; }

    bool isItemSelected(unsigned int index) const;
    bool isItemEnabled(unsigned int index) const;
    bool isItemSelectable(unsigned int index) const;
    FbTk::ThemeProxy<MenuTheme> &theme() { return m_theme; }
    const FbTk::ThemeProxy<MenuTheme> &theme() const { return m_theme; }
    unsigned char alpha() const { return theme()->alpha(); }
    static Menu *shownMenu() { return shown; }
    static Menu *focused() { return s_focused; }
    static void hideShownMenu();
    /// @return menuitem at index
    const MenuItem *find(unsigned int index) const { return menuitems[index]; }
    MenuItem *find(unsigned int index) { return menuitems[index]; }
    //@}
    /// @return true if index is valid
    bool validIndex(int index) const { return (index < static_cast<int>(numberOfItems()) && index >= 0); }

    Menu *parent() { return m_parent; }
    const Menu *parent() const { return m_parent; }

    void renderForeground(FbWindow &win, FbDrawable &drawable);

protected:

    void setTitleVisibility(bool b) {
        m_title_vis = b; m_need_update = true;
        if (!b)
            titleWindow().lower();
        else
            titleWindow().raise();
    }

    // renders item onto pm
    int drawItem(FbDrawable &pm, unsigned int index,
                 bool highlight = false,
                 bool exclusive_drawable = false);
    void clearItem(int index, bool clear = true, int search_index = -1);
    void highlightItem(int index);
    virtual void redrawTitle(FbDrawable &pm);
    virtual void redrawFrame(FbDrawable &pm);

    virtual void internal_hide(bool first = true);

    virtual void update(FbTk::Subject *);

private:

    void openSubmenu();
    void closeMenu();
    void startHide();
    void stopHide();

    FbTk::ThemeProxy<MenuTheme> &m_theme;
    Menu *m_parent;
    ImageControl &m_image_ctrl;

    typedef std::vector<MenuItem *> Menuitems;
    Menuitems menuitems;
    TypeAhead<Menuitems, MenuItem *> m_type_ahead;
    Menuitems m_matches;

    void resetTypeAhead();
    void drawTypeAheadItems();
    void drawLine(int index, int size);
    void fixMenuItemIndices();

    int m_screen_x, m_screen_y;
    unsigned int m_screen_width, m_screen_height;
    bool m_moving; ///< if we're moving/draging or not
    bool m_closing; ///< if we're right clicking on the menu title
    bool m_visible; ///< menu visibility
    bool m_torn; ///< torn from parent
    bool m_internal_menu; ///< whether we should destroy this menu or if it's managed somewhere else
    bool m_title_vis; ///< title visibility

    int m_which_sub;
    Alignment m_alignment;

    struct _menu {
        Pixmap frame_pixmap, title_pixmap, hilite_pixmap;
        FbTk::FbWindow window, frame, title;

        std::string label;
        int x_move, y_move, sublevels, persub, minsub, grab_x, grab_y;

        unsigned int frame_h, item_w;
    } menu;

    int m_active_index; ///< current highlighted index

    std::auto_ptr<FbTk::Shape> m_shape;

    Drawable m_root_pm;
    static Menu *shown; ///< used for determining if there's a menu open at all
    static Menu *s_focused; ///< holds current input focused menu, so one can determine if a menu is focused
    bool m_need_update;
    Timer m_submenu_timer;
    Timer m_hide_timer;
};

} // end namespace FbTk

#endif // FBTK_MENU_HH