// -*- C++ -*-
/* GG is a GUI for SDL and OpenGL.
   Copyright (C) 2003 T. Zachary Laine

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public License
   as published by the Free Software Foundation; either version 2.1
   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
   Lesser General Public License for more details.
    
   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA

   If you do not wish to comply with the terms of the LGPL please
   contact the author as other terms are available for a fee.
    
   Zach Laine
   whatwasthataddress@hotmail.com */

/** \file Menu.h
    Contains the MenuItem class, which represents menu data; the MenuBar control class; and the PopupMenu class, which is 
    used to provide immediate context menus. */

#ifndef _GG_Menu_h_
#define _GG_Menu_h_

#include <GG/Control.h>


namespace GG {

class Font;
class TextControl;

/** serves as a single menu entry in a GG::MenuBar or GG::PopupMenu; may include a submenu.  All legal item_IDs are
    positive (and so non-zero); any item_ID <= 0 is considered invalid.  Each MenuItem has a signal that is emmitted
    with its menu_ID member whenever it is selected. Such signals may be emitted even when the menu_ID is 0.  These
    signals allow each MenuItem to be attached directly to code that should be executed when that item is selected.
    Note that the signal is not serialized.  The user must restore it after the MenuItem is reloaded. */
struct GG_API MenuItem
{
    /** \name Signal Types */ //@{
    typedef boost::signal<void (int)> SelectedIDSignalType; ///< invokes the appropriate functor to handle the menu selection, and passes the ID assigned to the item
    typedef boost::signal<void ()>    SelectedSignalType;   ///< invokes the appropriate functor to handle the menu selection
    //@}

    /** \name Slot Types */ //@{
    typedef SelectedIDSignalType::slot_type SelectedIDSlotType; ///< type of functor(s) invoked on a SelectedSignalType
    typedef SelectedSignalType::slot_type   SelectedSlotType;   ///< type of functor(s) invoked on a SelectedSignalType
    //@}

    /** \name Structors */ //@{
    MenuItem(); ///< default ctor
    MenuItem(const std::string& str, int id, bool disable, bool check); ///< ctor

    /** ctor that allows direct attachment of this item's signal to a "slot" function or functor */
    MenuItem(const std::string& str, int id, bool disable, bool check, const SelectedIDSlotType& slot);

    /** ctor that allows direct attachment of this item's signal to a "slot" function or functor */
    MenuItem(const std::string& str, int id, bool disable, bool check, const SelectedSlotType& slot);

    /** ctor that allows direct attachment of this item's signal to a "slot" member function of a specific object */
    template <class T1, class T2>
    MenuItem(const std::string& str, int id, bool disable, bool check, void (T1::* slot)(int), T2* obj);

    /** ctor that allows direct attachment of this item's signal to a "slot" member function of a specific object */
    template <class T1, class T2>
    MenuItem(const std::string& str, int id, bool disable, bool check, void (T1::* slot)(), T2* obj);

    virtual ~MenuItem(); ///< virtual dtor
    //@}

    /** \name Accessors */ //@{
    mutable boost::shared_ptr<SelectedIDSignalType> SelectedIDSignal; ///< the selected signal object for this MenuItem that conveys the selected menu item ID
    mutable boost::shared_ptr<SelectedSignalType>   SelectedSignal;   ///< the selected signal object for this MenuItem
    //@}

    std::string           label;      ///< text shown for this menu item
    int                   item_ID;    ///< ID number associated with this menu item
    bool                  disabled;   ///< set to true when this menu item is disabled
    bool                  checked;    ///< set to true when this menu item can be toggled, and is currently on
    std::vector<MenuItem> next_level; ///< submenu off of this menu item; may be emtpy

private:
    friend class boost::serialization::access;
    template <class Archive>
    void serialize(Archive& ar, const unsigned int version);
};


struct SetFontAction;
struct SetTextColorAction;

/** a menu bar control providing "browse" updates to user navigation of the menu.  Whenever a menu item is selected, a
    signal is emitted which includes the ID of the selected item.  It is recommended that the user attach each menu item
    to an appropriate function the will execute the actions associated with the menu item, rather than attaching all the
    items to a single slot which uses the int ID parameter to deduce the appropriate action.  The int ID parameter is
    best used when there are several menu items that should execute the same code with different parameters.  For
    instance, if a submenu contains a list of recently used files, each item that contains a filename might be attached
    to a Reopen(int) function, and the int can be used to determine which file from the list should be opened. If some
    action is to be taken as the user browses the menu items, such as displaying some visual cue to indicate the result
    of chosing a particular menu entry, you can attach a slot function to the BrowsedSignalType object returned by
    BrowsedSignal.  Whenever the mouse moves to a new menu item, this signal is emitted with the ID number of the item
    under the cursor.  */
class GG_API MenuBar : public Control
{
public:
    /** \name Signal Types */ //@{
    typedef boost::signal<void (int)> BrowsedSignalType; ///< emits the ID of an item in the menu when the cursor moves over it
    //@}

    /** \name Slot Types */ //@{
    typedef BrowsedSignalType::slot_type  BrowsedSlotType;   ///< type of functor(s) invoked on a BrowsedSignalType
    //@}

    /** \name Structors */ //@{
    /** ctor.  Parameter \a m should contain the desired menu in its next_level member. */
    MenuBar(int x, int y, int w, const boost::shared_ptr<Font>& font, Clr text_color = CLR_WHITE, Clr color = CLR_BLACK, Clr interior = CLR_SHADOW); ///< ctor
    MenuBar(int x, int y, int w, const boost::shared_ptr<Font>& font, const MenuItem& m, Clr text_color = CLR_WHITE, Clr color = CLR_BLACK, Clr interior = CLR_SHADOW); ///< ctor that takes a MenuItem containing menus with which to populate the MenuBar
    //@}

    /** \name Accessors */ //@{
    virtual Pt        MinUsableSize() const;

    const MenuItem&   AllMenus() const;                           ///< returns a const reference to the MenuItem that contains all the menus and their contents
    bool              ContainsMenu(const std::string& str) const; ///< returns true if there is a top-level menu in the MenuBar whose label is \a str
    int               NumMenus() const;                           ///< returns the number of top-level menus in the MenuBar

    /** returns a const reference to the top-level menu in the MenuBar whose label is \a str.  \note No check is made to ensure such a menu exists. */
    const MenuItem&   GetMenu(const std::string& str) const;

    const MenuItem&   GetMenu(int n) const;      ///< returns a const reference to the \a nth menu in the MenuBar; not range-checked

    Clr               BorderColor() const;       ///< returns the color used to render the border of the control
    Clr               InteriorColor() const;     ///< returns the color used to render the interior of the control
    Clr               TextColor() const;         ///< returns the color used to render menu item text
    Clr               HiliteColor() const;       ///< returns the color used to indicate a hilited menu item
    Clr               SelectedTextColor() const; ///< returns the color used to render a hilited menu item's text

    mutable BrowsedSignalType BrowsedSignal; ///< the browsed signal object for this PopupMenu
    //@}

    /** \name Mutators */ //@{
    virtual void   Render();
    virtual void   LButtonDown(const Pt& pt, Uint32 keys);
    virtual void   MouseHere(const Pt& pt, Uint32 keys);
    virtual void   MouseLeave();

    virtual void   SizeMove(const Pt& ul, const Pt& lr);

    MenuItem&      AllMenus();                    ///< returns a reference to the MenuItem that contains all the menus and their contents

    /** returns a reference to the top-level menu in the MenuBar whose label is \a str.  \note No check is made to ensure such a menu exists. */
    MenuItem&      GetMenu(const std::string& str);

    MenuItem&      GetMenu(int n);                ///< returns a reference to the \a nth menu in the MenuBar; not range-checked
    void           AddMenu(const MenuItem& menu); ///< adds \a menu to the end of the top level of menus

    void           SetBorderColor(Clr clr);       ///< sets the color used to render the border of the control
    void           SetInteriorColor(Clr clr);     ///< sets the color used to render the interior of the control
    void           SetTextColor(Clr clr);         ///< sets the color used to render menu item text
    void           SetHiliteColor(Clr clr);       ///< sets the color used to indicate a hilited menu item
    void           SetSelectedTextColor(Clr clr); ///< sets the color used to render a hilited menu item's text

    virtual void   DefineAttributes(WndEditor* editor);
    //@}

protected:
    /** \name Structors */ //@{
    MenuBar(); ///< default ctor
    //@}

    /** \name Accessors */ //@{
    const boost::shared_ptr<Font>&   GetFont() const;    ///< returns the font used to render text in the control
    const std::vector<TextControl*>& MenuLabels() const; ///< returns the text for each top-level menu item
    int                              Caret() const;      ///< returns the current position of the caret
    //@}

private:
    /** determines the rects in m_menu_labels, and puts the menus in multiple rows if they will not fit in one */
    void AdjustLayout(bool reset = false);

    void BrowsedSlot(int n); ///< responds to a browse in a PopupMenu submenu, and passes it along

    boost::shared_ptr<Font>   m_font;           ///< the font used to render the text in the control
    Clr                       m_border_color;   ///< the color of the menu's border
    Clr                       m_int_color;      ///< color painted into the client area of the control
    Clr                       m_text_color;     ///< color used to paint text in control
    Clr                       m_hilite_color;   ///< color behind selected items
    Clr                       m_sel_text_color; ///< color of selected text

    MenuItem                  m_menu_data;      ///< this is not just a single menu item; the next_level element represents the entire menu
    std::vector<TextControl*> m_menu_labels;    ///< the text for each top-level menu item
    int                       m_caret;          ///< the currently indicated top-level menu (open or under the cursor)

    friend struct SetFontAction;
    friend struct SetTextColorAction;

    friend class boost::serialization::access;
    template <class Archive>
    void serialize(Archive& ar, const unsigned int version);
};


/** this is a modal pop-up menu.  PopupMenu gives calling code the abiltiy to create a pop-up menu (usually in response
    to a right mouse click), allow the pop-up to execute, and then obtain an integer ID representing the selected menu
    item, by calling MenuID().  If no menu item has been selected, MenuID() returns 0.  Though every MenuItem in a
    PopupMenu may be attached to a slot directly, it is not recommended.  The intent of this class is to act as a tool
    to get immediate input from the user, inline.  However, attaching MenuItem signals directly to slots will work, and
    it will certainly be useful in some cases to do this.  Note also that there is no way to serialize a PopupMenu.
    This is also because of the intent to use PopupMenus in an immediate, short-lived manner.  If you wish to save an
    often-used popup menu, simply create the MenuItem that the popup is based on, and save and load that.  Also, if some
    action is to be taken as the user browses the menu items, such as displaying some visual cue to indicate the result
    of chosing a particular menu entry, you can attach a slot function to the BrowsedSignalType object returned by
    BrowsedSignal.  Whenever the mouse moves to a new menu item, this signal is emitted with the ID number of the item
    under the cursor. */
class GG_API PopupMenu : public Wnd
{
public:
    /** \name Signal Types */ //@{
    typedef boost::signal<void (int)> BrowsedSignalType; ///< emits the ID of an item in the menu when the cursor moves over it
    //@}

    /** \name Slot Types */ //@{
    typedef BrowsedSignalType::slot_type  BrowsedSlotType;   ///< type of functor(s) invoked on a BrowsedSignalType
    //@}

    /** \name Structors */ //@{
    /** ctor.  Parameter \a m should contain the desired menu in its next_level member. */
    PopupMenu(int x, int y, const boost::shared_ptr<Font>& font, const MenuItem& m, Clr text_color = CLR_WHITE, Clr color = CLR_BLACK, Clr interior = CLR_SHADOW);
    //@}

    /** \name Accessors */ //@{
    virtual Pt  ClientUpperLeft() const;

    int         MenuID() const;            ///< returns the integer ID of the menu item selected by the user, or 0 if none was selected
    Clr         BorderColor() const;       ///< returns the color used to render the border of the control
    Clr         InteriorColor() const;     ///< returns the color used to render the interior of the control
    Clr         TextColor() const;         ///< returns the color used to render menu item text
    Clr         HiliteColor() const;       ///< returns the color used to indicate a hilited menu item
    Clr         SelectedTextColor() const; ///< returns the color used to render a hilited menu item's text

    mutable BrowsedSignalType BrowsedSignal; ///< the browsed signal object for this PopupMenu
    //@}

    /** \name Mutators */ //@{
    virtual void   Render();
    virtual void   LButtonUp(const Pt& pt, Uint32 keys);
    virtual void   LClick(const Pt& pt, Uint32 keys);
    virtual void   LDrag(const Pt& pt, const Pt& move, Uint32 keys);
    virtual void   RButtonUp(const Pt& pt, Uint32 keys);
    virtual void   RClick(const Pt& pt, Uint32 keys);
    virtual void   MouseHere(const Pt& pt, Uint32 keys);

    virtual int    Run();

    void           SetBorderColor(Clr clr);       ///< sets the color used to render the border of the control
    void           SetInteriorColor(Clr clr);     ///< sets the color used to render the interior of the control
    void           SetTextColor(Clr clr);         ///< sets the color used to render menu item text
    void           SetHiliteColor(Clr clr);       ///< sets the color used to indicate a hilited menu item
    void           SetSelectedTextColor(Clr clr); ///< sets the color used to render a hilited menu item's text
    //@}

protected:
    /** \name Accessors */ //@{
    const boost::shared_ptr<Font>&
                             GetFont() const;      ///< returns the font used to render text in the control
    const MenuItem&          MenuData() const;     ///< returns a const reference to the MenuItem that contains all the menu contents
    const std::vector<Rect>& OpenLevels() const;   ///< returns the bounding rectangles for each open submenu, used to detect clicks in them
    const std::vector<int>&  Caret() const;        ///< returns the stack representing the caret's location's path (eg 0th subitem of 1st subitem of item 3) back() is the most recent push
    const MenuItem*          ItemSelected() const; ///< returns the menu item selected (0 if none)
    //@}

private:
    boost::shared_ptr<Font>
                      m_font;           ///< the font used to render the text in the control
    Clr               m_border_color;   ///< the color of the menu's border
    Clr               m_int_color;      ///< color painted into the client area of the control
    Clr               m_text_color;     ///< color used to paint text in control
    Clr               m_hilite_color;   ///< color behind selected items
    Clr               m_sel_text_color; ///< color of selected text

    MenuItem          m_menu_data;   ///< this is not just a single menu item; the next_level element represents the entire menu

    std::vector<Rect> m_open_levels; ///< bounding rectangles for each open submenu, used to detect clicks in them
    std::vector<int>  m_caret;       ///< stack representing the caret's location's path (eg 0th subitem of 1st subitem of item 3) back() is the most recent push

    const Pt          m_origin;         ///< the upper left hand corner of the control's visible area
    MenuItem*         m_item_selected;  ///< the menu item selected (0 if none)
};

} // namespace GG

// template implemetations
template <class T1, class T2>
GG::MenuItem::MenuItem(const std::string& str, int id, bool disable, bool check, void (T1::* slot)(int), T2* obj) :
    SelectedIDSignal(new SelectedIDSignalType()),
    SelectedSignal(new SelectedSignalType()),
    label(str), 
    item_ID(id), 
    disabled(disable), 
    checked(check)
{
    SelectedIDSignal->connect(boost::bind(slot, obj, _1));
}

template <class T1, class T2>
GG::MenuItem::MenuItem(const std::string& str, int id, bool disable, bool check, void (T1::* slot)(), T2* obj) :
    SelectedIDSignal(new SelectedIDSignalType()),
    SelectedSignal(new SelectedSignalType()),
    label(str), 
    item_ID(id), 
    disabled(disable), 
    checked(check)
{
    SelectedSignal->connect(boost::bind(slot, obj));
}

template <class Archive>
void GG::MenuItem::serialize(Archive& ar, const unsigned int version)
{
    ar  & BOOST_SERIALIZATION_NVP(label)
        & BOOST_SERIALIZATION_NVP(item_ID)
        & BOOST_SERIALIZATION_NVP(disabled)
        & BOOST_SERIALIZATION_NVP(checked)
        & BOOST_SERIALIZATION_NVP(next_level);
}

template <class Archive>
void GG::MenuBar::serialize(Archive& ar, const unsigned int version)
{
    ar  & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Control)
        & BOOST_SERIALIZATION_NVP(m_font)
        & BOOST_SERIALIZATION_NVP(m_border_color)
        & BOOST_SERIALIZATION_NVP(m_int_color)
        & BOOST_SERIALIZATION_NVP(m_text_color)
        & BOOST_SERIALIZATION_NVP(m_hilite_color)
        & BOOST_SERIALIZATION_NVP(m_sel_text_color)
        & BOOST_SERIALIZATION_NVP(m_menu_data)
        & BOOST_SERIALIZATION_NVP(m_menu_labels)
        & BOOST_SERIALIZATION_NVP(m_caret);
}

#endif // _GG_Menu_h_
