/*
    ParaGUI - crossplatform widgetset
    Copyright (C) 2000,2001  Alexander Pipelka

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

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Alexander Pipelka
    pipelka@teleweb.at

    Last Update:      $Author: pipelka $
    Update Date:      $Date: 2001/06/06 21:33:38 $
    Source File:      $Source: /usr/local/CVSROOT/linux/paragui/include/Attic/pgpopupmenu.h,v $
    CVS/RCS Revision: $Revision: 1.1.2.9 $
    Status:           $State: Exp $
*/


#ifndef PG_POPUPMENU_H
#define PG_POPUPMENU_H

#include <string>
#include <list>
#ifdef HAVE_HASH_MAP
#include <hash_map>
#else
#include <map>
#endif

#include "pggradientwidget.h"

class PG_PopupMenu;

/**
 * @author Marek Habersack
 *
 * @short A menu item data structure
 *
 * Even though implemented as a class, MenuItem is not intended to be a new
 * kind of widget - therefore it is derived only from PG_Rect for
 * convenience. The idea is to have a "smart" data type (or a dumb class,
 * if you prefer) that knows how to perform simple and specific actions on
 * itself. The intention is to provide PG_PopupMenu with a fast mechanism
 * for processing menu items - if MenuItem was derived from any of ParaGUI
 * classes it would incurr unnecessary processing overhead.
 *
 * @todo better separator code
 * @todo icon drawing
 */
class MenuItem : public PG_Rect
{
  public: // types
    enum MI_FLAGS {
        MIF_NONE = 0,
        MIF_DISABLED = 0x01,
        MIF_SEPARATOR = 0x02,
        MIF_SUBMENU = 0x04
    };
    
  public: // methods
    MenuItem(PG_PopupMenu *parent,
             char *caption,
             int id,
             MI_FLAGS flags,
             TTF_Font *font = 0);
    MenuItem(PG_PopupMenu *parent,
             char *caption,
             PG_PopupMenu *submenu,
             TTF_Font *font = 0);
    ~MenuItem();
    
    bool measureItem(PG_Rect* rect, TTF_Font *font = 0, bool full = false);
    bool isPointInside(int x, int y);
    inline void moveTo(int x, int y);

    inline SDL_Surface* getNormal() const;
    inline SDL_Surface* getDisabled() const;
    inline SDL_Surface* getSelected() const;
    
    bool paintNormal(SDL_Surface *canvas, SDL_Color *tcol, SDL_Color *scol = 0);
    bool paintDisabled(SDL_Surface *canvas, SDL_Color *tcol, SDL_Color *scol = 0);
    bool paintSelected(SDL_Surface *canvas, SDL_Color *tcol, SDL_Color *scol = 0);

    inline void disable();
    inline void enable();
    inline bool isDisabled() const;
    inline bool isEnabled() const;
    inline void select();
    inline void unselect();
    inline bool isSelected() const;
    inline bool isSeparator() const;
    inline bool isSubMenu() const;
    inline bool isMute() const;
    
    inline int Width() const;
    inline int Height() const;

    inline int getId() const;
    inline PG_PopupMenu *getSubMenu() const;

    inline const std::string& getCaption() const;

#ifndef SWIG
    inline operator PG_Point const&() const;
#endif

  private: // methods
    void initItem();
    bool renderSurface(SDL_Surface *canvas, SDL_Surface **text, SDL_Color *tcol, SDL_Color *scol = 0);
    bool isValidRect();
    
  protected: // data
    unsigned      myFlags;
    std::string   myCaption;
    PG_PopupMenu *myParent;
    
    TTF_Font     *myFont;
    PG_PopupMenu *mySubMenu;
    int           myId;

    SDL_Surface  *sNormal;
    SDL_Surface  *sSelected;
    SDL_Surface  *sDisabled;
    
    bool          selected;
    
  private: // data
    bool          needRecalc;
    SDL_Rect      blitRect;
    PG_Point      myPoint;
};

inline void MenuItem::moveTo(int _x, int _y)
{
    myPoint.x = x = _x;
    myPoint.y = y = _y;
};

inline SDL_Surface* MenuItem::getNormal() const
{
    return sNormal;
}

inline SDL_Surface* MenuItem::getDisabled() const
{
    return sDisabled;
}

inline SDL_Surface* MenuItem::getSelected() const
{
    return sSelected;
}

inline void MenuItem::disable()
{
    myFlags |= MIF_DISABLED;
}

inline void MenuItem::enable()
{
    myFlags &= ~MIF_DISABLED;
}

inline bool MenuItem::isDisabled() const
{
    return (myFlags & MIF_DISABLED);
}

inline bool MenuItem::isEnabled() const
{
    return !(myFlags & MIF_DISABLED);
}

inline void MenuItem::select()
{
    selected = true;
}

inline void MenuItem::unselect()
{
    selected = false;
}

inline bool MenuItem::isSelected() const
{
    return selected;
}

inline bool MenuItem::isSeparator() const
{
    return (myFlags & MIF_SEPARATOR);
}

inline bool MenuItem::isSubMenu() const
{
    return (myFlags & MIF_SUBMENU);
}

inline bool MenuItem::isMute() const
{
    return ((myFlags & MIF_DISABLED) ||
            (myFlags & MIF_SEPARATOR));
}

inline int MenuItem::Width() const
{
    return my_width;
}

inline int MenuItem::Height() const
{
    return my_height;
}

inline int MenuItem::getId() const
{
    return myId;
}

inline PG_PopupMenu *MenuItem::getSubMenu() const
{
    return mySubMenu;
}

inline const std::string& MenuItem::getCaption() const
{
    return myCaption;
}

inline MenuItem::operator PG_Point const&() const
{
    return myPoint;
}

typedef std::list<MenuItem*>::iterator MII;
typedef bool (*ActionFunc)(MenuItem *item, int id, void *data);
typedef bool (PG_DrawObject::*ActionMethod)(MenuItem *item, int id, void *data);

/**
 * @author Marek Habersack
 *
 * @short A menu item action representation
 *
 * This non-visual class is a simple wrapper around several methods of
 * handling menu item clicks. Note that the usage of this class is not
 * limited just to menus. You can centralize action handling by using
 * objects of this class and careful menu ID (or other object ID)
 * selection.
 *
 */
class PG_Action
{
  public:
    /**
     * @short Constructor for non-member handler.
     *
     * Construct an action using the provided non-member function as
     * the handler associated with menu item assigned the passed menu
     * ID. This handler type can also be used with static class member
     * function.
     *
     * @param id        corresponding menu item ID
     * @param handler   function to handle menu click
     */
    PG_Action(int id, ActionFunc handler, void *data = 0) 
            : myHandlerFun(handler),
              myObject(0),
              myHandlerMethod(0),
              myId(id),
              myData(data)
    {}

    /**
     * @short Constructor for member handler
     *
     * Construct an action using the provided object and its specified
     * member function as the handler associated with menu item assigned
     * the passed menu ID.
     *
     * @param id        corresponding menu item ID
     * @param obj       object owning the handler
     * @param handler   member function (method) to handle the item click
     */
    PG_Action(int id, PG_DrawObject *obj, ActionMethod handler, void *data = 0)
            : myHandlerFun(0),
              myObject(obj),
              myHandlerMethod(handler),
              myId(id),
              myData(data)
    {}

    virtual ~PG_Action() {};
    
    /**
     * @short Handle the menu item click
     *
     * Invokes the installed handler for the associated menu item ID.
     *
     * @param item  MenuItem that generated this action.
     *
     * @return true on success, false on failure
     */
    inline virtual bool execute(MenuItem *item) const;

  protected:
    ActionFunc      myHandlerFun;
    PG_DrawObject  *myObject;
    ActionMethod    myHandlerMethod;
    int             myId;
    void           *myData;
};

inline bool PG_Action::execute(MenuItem *item) const
{
    if (myHandlerMethod && myObject)
        return (myObject->*myHandlerMethod)(item, myId, myData);
    if (myHandlerFun)
        return myHandlerFun(item, myId, myData);

    return false;
}

#ifdef HAVE_HASH_MAP
typedef std::hash_map<int, PG_Action*> action_map;
#else
typedef std::map<int, PG_Action*> action_map;
#endif

/**
 * @author Marek Habersack
 *
 * @short A stand-alone or attached popup menu
 *
 * A popup menu that can be attached to a menu bar or used
 * stand-alone. In the first case, the widget sizes itself so that
 * only the caption is visible, the menu items remain hidden until
 * menu is activated. In the latter case menu sizes itself to
 * encompass all of its items unless the menu size would exceed the
 * space between the menu origin and the edge of the screen. In such
 * case, menu displays a "scroller" button at the bottom.
 *
 * @todo implement the scroller code (menu scrolling when it exceeds the
 *       screen height/width - a kind of specialized widgetlist). Should
 *       display a scroller icon at the bottom/top of the popup menu - a'la
 *       w2k.
 * @todo keyboard handling (accelerators, ESC/ENTER & arrows)
 * @todo caption should be optional
 */
class DECLSPEC PG_PopupMenu : public PG_GradientWidget
{
  public: // methods  

    PG_PopupMenu(PG_Widget *parent,
                 int x, int y,
                 char *caption,
                 char *style = "PopupMenu");

    ~PG_PopupMenu();    

    /** @name Add a new menu item to this menu
     *
     * Constructs a new menu item using the provided parameters and
     * then adds the item to this menu.
     *@{
     */
    
    /**
     * Adds a menu item whose handler (if any) is set to be a stand-alone
     * function.
     *
     * @param caption   the item caption
     * @param ID        the item identifier
     * @param handler   function to handle the menu item click
     * @param data      application-specific data associated with the menu
     *                  item action.
     * @param flags     menu item flags
     *
     */
    PG_PopupMenu& addMenuItem(char *caption,
                              int ID,
                              ActionFunc handler = 0,
                              void *data = 0,
                              MenuItem::MI_FLAGS flags = MenuItem::MIF_NONE);

    /**
     * Adds a menu item whose handler (if any) is set to be a member
     * method of some object.
     *
     * @param caption   the item caption
     * @param ID        the item identifier
     * @param handler   function to handle the menu item click
     * @param obj       object the 'handler' function lives in.
     * @param data      application-specific data associated with the menu
     *                  item action.
     * @param flags     menu item flags
     *
     * @note @code obj @endcode cannot be 0 if @code handler @endcode is present
     */
    PG_PopupMenu& addMenuItem(char *caption,
                              int ID,
                              ActionMethod handler,
                              PG_DrawObject *obj,
                              void *data = 0,
                              MenuItem::MI_FLAGS flags = MenuItem::MIF_NONE);

    PG_PopupMenu& addMenuItem(char *caption,
                              PG_PopupMenu *sub,
                              MenuItem::MI_FLAGS flags = MenuItem::MIF_SUBMENU);
    
    /**
     * Adds a menu item and accepts a pre-constructed action to become its
     * handler.
     *
     * @param caption   the item caption
     * @param ID        the item identifier
     * @param action    pre-constructed action for this item.
     * @param data      application-specific data associated with the menu
     *                  item action.
     * @param flags     menu item flags
     *
     */
    PG_PopupMenu& addMenuItem(char *caption,
                              int ID,
                              PG_Action *action,
                              void *data = 0,
                              MenuItem::MI_FLAGS flags = MenuItem::MIF_NONE);

    /**
     * @return a reference to this menu
     *@}
     */

    PG_PopupMenu& addSeparator();
    
    inline int maxItemWidth() const;

    void disableItem(int dd);
    void enableItem(int id);

    /**
     * Modal popup menu will be shown - i.e. all mouse/keyboard events will
     * be captured by the menu and until the user selects any menu item (or
     * cancels the menu by pressing ESC) no other widget will be
     * accessible. If @code x @endcoce and @code y @endcode are absent, menu is popped up at
     * its current position.
     *
     * @param x  xpos where the menu should pop up
     * @param y  ypos where the menu should pop up
     *
     */
    void trackMenu(int x = -1, int y = -1);
    
  protected: // methods
    // reimplemented
    void eventDraw(SDL_Surface *surface, const PG_Rect &rect);

    bool eventMouseMotion(const SDL_MouseMotionEvent *motion);
    bool eventMouseButtonDown(const SDL_MouseButtonEvent *button);
    bool eventMouseButtonUp(const SDL_MouseButtonEvent *button);
    bool eventKeyDown(const SDL_KeyboardEvent *key);
    
    void eventMoveWindow(int x, int y);
    void eventShow();
    void eventHide();
    
    void LoadThemeStyle(const char *widgettype);
    void LoadThemeStyle(const char *widgettype, const char *objectname);
    
    // own
    virtual bool getCaptionHeight(PG_Rect &rect, bool constructing = false);
    virtual void recalcRect();
    virtual void handleClick(int x, int y);
    virtual void enslave(PG_PopupMenu *master);
    virtual void liberate();
    
  private: // methods
    bool selectItem(MenuItem *item, MII iter);
    bool handleMotion(PG_Point const&);
    void appendItem(MenuItem *item);
    
  protected: // data
    std::list<MenuItem*>  items; /** the menu items collection */
    action_map            actions; /** menu item handlers */
    std::string          *myCaption; /** menu caption */

    SDL_Color             captionActiveColor;
    SDL_Color             captionInactiveColor;

    SDL_Color             miNormalColor;
    SDL_Color             miSelectedColor;
    SDL_Color             miDisabledColor;

    SDL_Color             sepNormalColor;
    SDL_Color             sepShadowColor;
    
    int                   xPadding;
    int                   yPadding;
    
  private: // data
    PG_Rect               captionRect;
    PG_Rect               actionRect;
    SDL_Surface          *captionSurface;

    PG_Gradient          *miGradients[3];
    SDL_Surface          *miBackgrounds[3];
    int                   miBkModes[3];
    Uint8                 miBlends[3];
    int                   itemHeight;
    int                   lastH;
    MenuItem             *selected;

    bool                  tracking;
    bool                  wasTracking;
    bool                  buttonDown;

    MII                   stop;
    MII                   start;
    MII                   current;

    PG_PopupMenu         *activeSub;
    PG_PopupMenu         *myMaster;
    MenuItem             *subParent;
};

inline int PG_PopupMenu::maxItemWidth() const
{
    return my_width - xPadding;
}

#endif // PG_POPUPMENU_H
