// * This makes emacs happy -*-Mode: C++;-*-
/****************************************************************************
 * Copyright (c) 1998,1999,2000 Free Software Foundation, Inc.              *
 *                                                                          *
 * 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, distribute with modifications, 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 ABOVE 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.                               *
 *                                                                          *
 * Except as contained in this notice, the name(s) of the above copyright   *
 * holders shall not be used in advertising or otherwise to promote the     *
 * sale, use or other dealings in this Software without prior written       *
 * authorization.                                                           *
 ****************************************************************************/

/****************************************************************************
 *   Author: Juergen Pfeifer, 1997                                          *
 *   Contact: http://www.familiepfeifer.de/Contact.aspx?Lang=en             *
 ****************************************************************************/

// $Id: cursesm.h,v 1.16 2002/07/06 15:47:52 juergen Exp $

#ifndef NCURSES_CURSESM_H_incl
#define NCURSES_CURSESM_H_incl 1

#include <ncurses/cursesp.h>

extern "C" {
#  include <ncurses/menu.h>
}
//
// -------------------------------------------------------------------------
// This wraps the ITEM type of <ncurses/menu.h>
// -------------------------------------------------------------------------
//
class NCURSES_IMPEXP NCursesMenuItem {
  friend class NCursesMenu;
  
protected:
  ITEM *item;

  inline void OnError (int err) const THROWS(NCursesMenuException) {
    if (err != E_OK)
      THROW(new NCursesMenuException (err));
  }

public:
  NCursesMenuItem (const char* p_name     = NULL,
		   const char* p_descript = NULL ) { 
    item = p_name ? ::new_item (p_name, p_descript) : (ITEM*)0; 
    if (p_name && !item)
      OnError (E_SYSTEM_ERROR);
  }
  // Create an item. If you pass both parameters as NULL, a delimiting
  // item is constructed which can be used to terminate a list of
  // NCursesMenu objects.
  
  virtual ~NCursesMenuItem ();
  // Release the items memory

  inline const char* name () const {
    return ::item_name (item);
  }
  // Name of the item

  inline const char* description () const {
    return ::item_description (item);
  }
  // Description of the item
  
  inline int (index) (void) const {
    return ::item_index (item);
  }
  // Index of the item in an item array (or -1)

  inline void options_on (Item_Options options) {
    OnError (::item_opts_on (item, options));
  }
  // Switch on the items options

  inline void options_off (Item_Options options) {
    OnError (::item_opts_off (item, options));
  }
  // Switch off the item's option

  inline Item_Options options () const {
    return ::item_opts (item);
  }
  // Retrieve the items options

  inline void set_options (Item_Options options) {
    OnError (::set_item_opts (item, options));
  }
  // Set the items options

  inline void set_value (bool f) {
    OnError (::set_item_value (item,f));
  }
  // Set/Reset the items selection state

  inline bool value () const {
    return ::item_value (item);
  }
  // Retrieve the items selection state
  
  inline bool visible () const {
    return ::item_visible (item);
  }
  // Retrieve visibility of the item

  virtual bool action();
  // Perform an action associated with this item; you may use this in an
  // user supplied driver for a menu; you may derive from this class and
  // overload action() to supply items with different actions.
  // If an action returns true, the menu will be exited. The default action
  // is to do nothing.
};

// Prototype for an items callback function.
typedef bool ITEMCALLBACK(NCursesMenuItem&);

// If you don't like to create a child class for individual items to
// overload action(), you may use this class and provide a callback
// function pointer for items.
class NCURSES_IMPEXP NCursesMenuCallbackItem : public NCursesMenuItem {
private:
  ITEMCALLBACK* p_fct;

public:
  NCursesMenuCallbackItem(ITEMCALLBACK* fct       = NULL,
			  const char* p_name      = NULL,
			  const char* p_descript  = NULL )
    : NCursesMenuItem (p_name, p_descript),
      p_fct (fct) {
  }
  
  virtual ~NCursesMenuCallbackItem();

  bool action();
};
//
// -------------------------------------------------------------------------
// This wraps the MENU type of <ncurses/menu.h>
// -------------------------------------------------------------------------
//
class NCURSES_IMPEXP NCursesMenu : public NCursesPanel {
protected:
  MENU *menu;

private:
  NCursesWindow* sub;   // the subwindow object
  bool b_sub_owner;     // is this our own subwindow?
  bool b_framed;        // has the menu a border?
  bool b_autoDelete;    // Delete items when deleting menu?

  NCursesMenuItem** my_items; // The array of items for this menu

  // This structure is used for the menu's user data field to link the
  // MENU* to the C++ object and to provide extra space for a user pointer.
  typedef struct {
    void*              m_user;      // the pointer for the user's data
    const NCursesMenu* m_back;      // backward pointer to C++ object
    const MENU*        m_owner;
  } UserHook;

  // Get the backward pointer to the C++ object from a MENU
  static inline NCursesMenu* getHook(const MENU *m) {
    UserHook* hook = (UserHook*)::menu_userptr(m);
    assert(hook != 0 && hook->m_owner==m);
    return (NCursesMenu*)(hook->m_back);
  }

  // This are the built-in hook functions in this C++ binding. In C++ we use
  // virtual member functions (see below On_..._Init and On_..._Termination)
  // to provide this functionality in an object oriented manner.
  static void mnu_init(MENU *);
  static void mnu_term(MENU *);
  static void itm_init(MENU *);
  static void itm_term(MENU *);
  
  // Calculate ITEM* array for the menu
  ITEM** mapItems(NCursesMenuItem* nitems[]);
  
protected:
  // internal routines 
  inline void set_user(void *user) {
    UserHook* uptr = (UserHook*)::menu_userptr (menu);
    assert (uptr != 0 && uptr->m_back==this && uptr->m_owner==menu);
    uptr->m_user = user;
  }

  inline void *get_user() {
    UserHook* uptr = (UserHook*)::menu_userptr (menu);
    assert (uptr != 0 && uptr->m_back==this && uptr->m_owner==menu);
    return uptr->m_user;
  }  
  
  void InitMenu (NCursesMenuItem* menu[],
		 bool with_frame,
		 bool autoDeleteItems);

  inline void OnError (int err) const THROWS(NCursesMenuException) {
    if (err != E_OK)
      THROW(new NCursesMenuException (this, err));
  }
  
  // this wraps the menu_driver call.
  virtual int driver (int c) ;
  
  // 'Internal' constructor to create a menu without association to
  // an array of items.
  NCursesMenu( int  lines, 
	       int  cols, 
	       int  begin_y = 0, 
	       int  begin_x = 0) 
    : NCursesPanel(lines,cols,begin_y,begin_x), 
      menu ((MENU*)0) {
  }

public:
  // Make a full window size menu
  NCursesMenu (NCursesMenuItem* Items[],
	       bool with_frame=FALSE,        // Reserve space for a frame?
	       bool autoDelete_Items=FALSE)  // Autocleanup of Items?
    : NCursesPanel() {
      InitMenu(Items, with_frame, autoDelete_Items);
  }

  // Make a menu with a window of this size.
  NCursesMenu (NCursesMenuItem* Items[], 
	       int  lines, 
	       int  cols, 
	       int  begin_y = 0, 
	       int  begin_x = 0,
	       bool with_frame=FALSE,        // Reserve space for a frame?
	       bool autoDelete_Items=FALSE)  // Autocleanup of Items?
    : NCursesPanel(lines, cols, begin_y, begin_x) {
      InitMenu(Items, with_frame, autoDelete_Items);
  }

  virtual ~NCursesMenu ();

  // Retrieve the menus subwindow
  inline NCursesWindow& subWindow() const {
    assert(sub!=NULL);
    return *sub;
  }

  // Set the menus subwindow
  void setSubWindow(NCursesWindow& sub);

  // Set these items for the menu
  inline void setItems(NCursesMenuItem* Items[]) {
    OnError(::set_menu_items(menu,mapItems(Items)));    
  }

  // Remove the menu from the screen
  inline void unpost (void) { 
    OnError (::unpost_menu (menu)); 
  }
  
  // Post the menu to the screen if flag is true, unpost it otherwise
  inline void post(bool flag = TRUE) {
    flag ? OnError (::post_menu(menu)) : OnError (::unpost_menu (menu)); 
  }

  // Get the numer of rows and columns for this menu
  inline void scale (int& mrows, int& mcols) const  {
    OnError (::scale_menu (menu, &mrows, &mcols));
  }

  // Set the format of this menu
  inline void set_format(int mrows, int mcols) {
    OnError (::set_menu_format(menu, mrows, mcols));
  }
  
  // Get the format of this menu
  inline void menu_format(int& rows,int& cols) { 
    ::menu_format(menu,&rows,&cols); 
  }
  
  // Items of the menu
  inline NCursesMenuItem* items() const {
    return *my_items; 
  }

  // Get the number of items in this menu
  inline int count() const {
    return ::item_count(menu); 
  }

  // Get the current item (i.e. the one the cursor is located)
  inline NCursesMenuItem* current_item() const {
    return my_items[::item_index(::current_item(menu))];
  }
  
  // Get the marker string
  inline const char* mark() const {
    return ::menu_mark(menu);
  }

  // Set the marker string
  inline void set_mark(const char *mark) {
    OnError (::set_menu_mark (menu, mark));
  }

  // Get the name of the request code c
  inline static const char* request_name(int c) {
    return ::menu_request_name(c);
  }

  // Get the current pattern
  inline char* pattern() const {
    return ::menu_pattern(menu);
  }

  // true if there is a pattern match, false otherwise.
  bool set_pattern (const char *pat);

  // set the default attributes for the menu
  // i.e. set fore, back and grey attribute
  virtual void setDefaultAttributes();

  // Get the menus background attributes
  inline chtype back() const {
    return ::menu_back(menu);
  }

  // Get the menus foreground attributes
  inline chtype fore() const {
    return ::menu_fore(menu);
  }

  // Get the menus grey attributes (used for unselectable items)
  inline chtype grey() const {
    return ::menu_grey(menu);
  }

  // Set the menus background attributes
  inline chtype set_background(chtype a) {
    return ::set_menu_back(menu,a);
  }

  // Set the menus foreground attributes
  inline chtype set_foreground(chtype a) {
    return ::set_menu_fore(menu,a);
  }

  // Set the menus grey attributes (used for unselectable items)
  inline chtype set_grey(chtype a) {
    return ::set_menu_grey(menu,a);
  }
  
  inline void options_on (Menu_Options opts) {
    OnError (::menu_opts_on (menu,opts));
  }

  inline void options_off(Menu_Options opts) {
    OnError (::menu_opts_off(menu,opts));
  }

  inline Menu_Options options() const {
    return ::menu_opts(menu);
  }
  
  inline void set_options (Menu_Options opts) {
    OnError (::set_menu_opts (menu,opts));
  }

  inline int pad() const {
    return ::menu_pad(menu);
  }

  inline void set_pad (int padch) {
    OnError (::set_menu_pad (menu, padch));
  }
  
  // Position the cursor to the current item
  inline void position_cursor () const {
    OnError (::pos_menu_cursor (menu));
  }

  // Set the current item
  inline void set_current(NCursesMenuItem& I) {
    OnError (::set_current_item(menu, I.item));
  }

  // Get the current top row of the menu
  inline int top_row (void) const {
    return ::top_row (menu);
  }

  // Set the current top row of the menu
  inline void set_top_row (int row) {
    OnError (::set_top_row (menu, row));
  }

  // spacing control
  // Set the spacing for the menu
  inline void setSpacing(int spc_description,
			 int spc_rows,
			 int spc_columns) {
    OnError(::set_menu_spacing(menu,
			       spc_description,
			       spc_rows,
			       spc_columns));
  }
  
  // Get the spacing info for the menu
  inline void Spacing(int& spc_description,
		      int& spc_rows,
		      int& spc_columns) const {
    OnError(::menu_spacing(menu,
			   &spc_description,
			   &spc_rows,
			   &spc_columns));
  }

  // Decorations
  inline void frame(const char *title=NULL, const char* btitle=NULL) {
    if (b_framed)
      NCursesPanel::frame(title,btitle);
    else
      OnError(E_SYSTEM_ERROR);
  }

  inline void boldframe(const char *title=NULL, const char* btitle=NULL) {
    if (b_framed)
      NCursesPanel::boldframe(title,btitle);
    else
      OnError(E_SYSTEM_ERROR);
  }
  
  inline void label(const char *topLabel, const char *bottomLabel) {
    if (b_framed)
      NCursesPanel::label(topLabel,bottomLabel);
    else
      OnError(E_SYSTEM_ERROR);
  }

  // -----
  // Hooks
  // -----

  // Called after the menu gets repositioned in its window.
  // This is especially true if the menu is posted.
  virtual void On_Menu_Init();

  // Called before the menu gets repositioned in its window.
  // This is especially true if the menu is unposted.
  virtual void On_Menu_Termination();

  // Called after the item became the current item
  virtual void On_Item_Init(NCursesMenuItem& item);

  // Called before this item is left as current item.
  virtual void On_Item_Termination(NCursesMenuItem& item);
  
  // Provide a default key virtualization. Translate the keyboard
  // code c into a menu request code.
  // The default implementation provides a hopefully straightforward
  // mapping for the most common keystrokes and menu requests.
  virtual int virtualize(int c);


  // Operators
  inline NCursesMenuItem* operator[](int i) const {
    if ( (i < 0) || (i >= ::item_count (menu)) )
      OnError (E_BAD_ARGUMENT);
    return (my_items[i]);
  }

  // Perform the menu's operation
  // Return the item where you left the selection mark for a single
  // selection menu, or NULL for a multivalued menu.
  virtual NCursesMenuItem* operator()(void);

  // --------------------
  // Exception handlers
  // Called by operator()
  // --------------------

  // Called if the request is denied
  virtual void On_Request_Denied(int c) const;
  
  // Called if the item is not selectable
  virtual void On_Not_Selectable(int c) const;

  // Called if pattern doesn't match
  virtual void On_No_Match(int c) const;

  // Called if the command is unknown
  virtual void On_Unknown_Command(int c) const;

};
//
// -------------------------------------------------------------------------
// This is the typical C++ typesafe way to allow to attach
// user data to an item of a menu. Its assumed that the user
// data belongs to some class T. Use T as template argument
// to create a UserItem.
// -------------------------------------------------------------------------
//
template<class T> class NCURSES_IMPEXP NCursesUserItem : public NCursesMenuItem
{
public:
  NCursesUserItem (const char* p_name,
		   const char* p_descript = NULL,
		   const T* p_UserData    = (T*)0)
    : NCursesMenuItem (p_name, p_descript) {
      if (item)
	OnError (::set_item_userptr (item, (void *)p_UserData));
  };

  virtual ~NCursesUserItem() {};

  inline const T* UserData (void) const {
    return (const T*)::item_userptr (item);
  };

  inline virtual void setUserData(const T* p_UserData) {
    if (item)
      OnError (::set_item_userptr (item, (void *)p_UserData));
  }
};
//
// -------------------------------------------------------------------------
// The same mechanism is used to attach user data to a menu
// -------------------------------------------------------------------------
//
template<class T> class NCURSES_IMPEXP NCursesUserMenu : public NCursesMenu
{
protected:
  NCursesUserMenu( int  lines, 
		   int  cols, 
		   int  begin_y = 0, 
		   int  begin_x = 0,
		   const T* p_UserData = (T*)0) 
    : NCursesMenu(lines,cols,begin_y,begin_x) {
      if (menu)
	set_user ((void *)p_UserData);
  }

public:
  NCursesUserMenu (NCursesMenuItem Items[],
		   const T* p_UserData = (T*)0,
		   bool with_frame=FALSE,
		   bool autoDelete_Items=FALSE)
    : NCursesMenu (Items, with_frame, autoDelete_Items) {
      if (menu)
	set_user ((void *)p_UserData);
  };
  
  NCursesUserMenu (NCursesMenuItem Items[],
		   int lines, 
		   int cols, 
		   int begin_y = 0, 
		   int begin_x = 0,
		   const T* p_UserData = (T*)0,
		   bool with_frame=FALSE)
    : NCursesMenu (Items, lines, cols, begin_y, begin_x, with_frame) {
      if (menu)
	set_user ((void *)p_UserData);
  };  
  
  virtual ~NCursesUserMenu() {
  };
  
  inline T* UserData (void) const {
    return (T*)get_user ();
  };

  inline virtual void setUserData (const T* p_UserData) {
    if (menu)
      set_user ((void *)p_UserData);
  }
};

#endif // NCURSES_CURSESM_H_incl
