// @configure_input@

/**************************************************************************\
 * Copyright (c) Kongsberg Oil & Gas Technologies AS
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of the copyright holder nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\**************************************************************************/

/*!
  \class So@Gui@PopupMenu Inventor/@Gui@/widgets/So@Gui@PopupMenu.h
  \brief The So@Gui@PopupMenu class implements a common interface for pop-up
  menu management for all the Coin GUI toolkit libraries.

  The point of this class is to enable client code to be exactly the
  same for all GUI libraries, even without being template-based.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H

#include <cassert>

#include <Inventor/SoLists.h>
#include <Inventor/errors/SoDebugError.h>

#include <so@gui@defs.h>
#include <Inventor/@Gui@/widgets/So@Gui@PopupMenu.h>
#include <Inventor/@Gui@/widgets/@Gui@NativePopupMenu.h>
#include <Inventor/@Gui@/common/SbGuiList.h>

// *************************************************************************

// Documentation for abstract methods.

/*!
  \fn void So@Gui@PopupMenu::popUp(@WIDGET@ inside, int x, int y)

  Invokes this pop-up menu instance.

  The coordinates for the pop-up will be used relative to the origo of
  the widget argument.
*/

// *************************************************************************

// The private data for the So@Gui@PopupMenu.

class So@Gui@PopupMenuP {
public:
  SbGuiList<int> menuitems;
  SbGuiList<int> radiogroups;
  SbPList * callbacks;
  SbPList * userdata;
};


#define PRIVATE(o) (o->pimpl)

// *************************************************************************

/*!
  Instantiation method.

  So@Gui@PopupMenu is implemented with this "factory" method instead of
  a public constructor to provide a completely generic interface on
  top of the toolkit's native pop-up menu implementation.
*/
So@Gui@PopupMenu *
So@Gui@PopupMenu::createInstance(void)
{
  return new @Gui@NativePopupMenu();
}


/*!
  The class constructor is protected, as So@Gui@PopupMenu uses a factory
  instantiation method.
*/
So@Gui@PopupMenu::So@Gui@PopupMenu(void)
{
  PRIVATE(this) = new So@Gui@PopupMenuP;

  PRIVATE(this)->callbacks = NULL;
  PRIVATE(this)->userdata = NULL;
} // So@Gui@PopupMenu()

/*!
  Destructor.
*/
So@Gui@PopupMenu::~So@Gui@PopupMenu()
{
  delete PRIVATE(this)->callbacks;
  delete PRIVATE(this)->userdata;
  delete PRIVATE(this);
} // ~So@Gui@PopupMenu()

// *************************************************************************

void
So@Gui@PopupMenu::setMenuItemMarked(int itemid, SbBool marked)
{
  // forward to native implementation
  this->_setMenuItemMarked(itemid, marked);

  if (marked) {
    const int radiogroupid = this->getRadioGroup(itemid);
    if (radiogroupid != -1) {
      this->setRadioGroupMarkedItem(itemid);
    }
  }
}

// *************************************************************************

/*!
*/

int
So@Gui@PopupMenu::newRadioGroup(
  int groupid)
{
  const int numGroupedItems = PRIVATE(this)->radiogroups.getLength();
  int id = groupid;
  if (id != -1) {
    SbBool hit = FALSE;
    int i;
    for (i = 0; i < numGroupedItems && hit == FALSE; i++) {
      if (PRIVATE(this)->radiogroups[i] == id)
        hit = TRUE;
    }
    if (hit) {
#if SO@GUI@_DEBUG
      SoDebugError::postInfo("So@Gui@PopupMenu::NewRadioGroup",
        "requested group id already taken");
#endif // SO@GUI@_DEBUG
      return -1; 
    }
  } else {
    id = 0;
    SbBool found;
    do {
      id++;
      found = FALSE;
      int i;
      for (i = 0; i < numGroupedItems && found == FALSE; i++) {
        if (PRIVATE(this)->radiogroups[i] == id)
          found = TRUE;
      }
    } while (found);
  }
  // id is OK here
  PRIVATE(this)->menuitems.append(-1); // fake menu item
  PRIVATE(this)->radiogroups.append(id); // register id in array
  return id;
} // newRadioGroup()

/*!
*/

int
So@Gui@PopupMenu::getRadioGroup(
  int itemid)
{
  assert(itemid != -1);
  const int numGroupedItems = PRIVATE(this)->radiogroups.getLength();
  int i;
  for (i = 0; i < numGroupedItems; i++) {
    if (PRIVATE(this)->menuitems[i] == itemid)
      return PRIVATE(this)->radiogroups[i];
  }
  return -1;
} // getRadioGroup()

/*!
*/

int
So@Gui@PopupMenu::getRadioGroupSize(
  int groupid)
{
  int count = 0;
  const int numItems = PRIVATE(this)->radiogroups.getLength();
  int i;
  for (i = 0; i < numItems; i++) {
    if (PRIVATE(this)->radiogroups[i] == groupid &&
         PRIVATE(this)->menuitems[i] != -1)
      count++;
  }
  return count;
} // getRadioGroupSize()

/*!
*/

void
So@Gui@PopupMenu::addRadioGroupItem(
  int groupid,
  int itemid)
{
  PRIVATE(this)->menuitems.append(itemid);
  PRIVATE(this)->radiogroups.append(groupid);
} // addRadioGroupItem()

/*!
*/

void
So@Gui@PopupMenu::removeRadioGroupItem(
  int itemid)
{
  assert(itemid != -1);
  const int numGroupedItems = PRIVATE(this)->menuitems.getLength();
  int i;
  for (i = 0; i < numGroupedItems; i++) {
    if (PRIVATE(this)->menuitems[i] == itemid) {
      PRIVATE(this)->menuitems.remove(i);
      PRIVATE(this)->radiogroups.remove(i);
      return;
    }
  }
} // removeRadioGroupItem()

// *************************************************************************

void
So@Gui@PopupMenu::setRadioGroupMarkedItem(int itemid)
{
  const int idx = PRIVATE(this)->menuitems.find(itemid);
  assert(idx != -1);
  const int groupid = PRIVATE(this)->radiogroups[idx];

  if (groupid == -1) {
    SoDebugError::post("So@Gui@PopupMenu::setRadioGroupMarkedItem",
                       "item not in a radio group");
    return;
  }

  const int numItems = PRIVATE(this)->menuitems.getLength();
  for (int i = 0; i < numItems; i++) {
    if (i == idx) { continue; }
    if (PRIVATE(this)->radiogroups[i] == groupid) {
      const int item = PRIVATE(this)->menuitems[i];
      if (item != -1) { // FIXME: should this be an assert? 20050622 mortene.
        if ( this->getMenuItemMarked(item) ) {
          this->setMenuItemMarked(item, FALSE);
        }
      }
    }
  }
}

int
So@Gui@PopupMenu::getRadioGroupMarkedItem(int groupid)
{
  const int numItems = PRIVATE(this)->menuitems.getLength();
  int i;
  for (i = 0; i < numItems; i++) {
    if (PRIVATE(this)->radiogroups[i] == groupid) {
      int item = PRIVATE(this)->menuitems[i];
      if (item != -1 && this->getMenuItemMarked(item))
        return item;
    }
  }
  return -1;
}

// *************************************************************************

/*!
*/

void
So@Gui@PopupMenu::addMenuSelectionCallback(
  So@Gui@MenuSelectionCallback * callback,
  void * user)
{
  if (PRIVATE(this)->callbacks == NULL) {
    PRIVATE(this)->callbacks = new SbPList;
    PRIVATE(this)->userdata = new SbPList;
  }
  PRIVATE(this)->callbacks->append((void *) callback);
  PRIVATE(this)->userdata->append(user);
} // addMenuSelectionCallback()

/*!
*/

void
So@Gui@PopupMenu::removeMenuSelectionCallback(
  So@Gui@MenuSelectionCallback * callback,
  void * user)
{
  if (PRIVATE(this)->callbacks == NULL) {
#if SO@GUI@_DEBUG
    SoDebugError::postInfo("So@Gui@PopupMenu::RemoveMenuSelectionCallback",
      "no callbacks at all");
#endif // SO@GUI@_DEBUG
    return;
  }
  int numCallbacks = PRIVATE(this)->callbacks->getLength();
  int i;
  for (i = 0; i < numCallbacks; i++) {
    if ((*PRIVATE(this)->callbacks)[i] == (void *) callback &&
         (*PRIVATE(this)->userdata)[i] == user) {
      PRIVATE(this)->callbacks->remove(i);
      PRIVATE(this)->userdata->remove(i);
      i--;
      numCallbacks--;
    }
  }
} // removeMenuSelectionCallback()

/*!
*/

void
So@Gui@PopupMenu::invokeMenuSelection(
  int itemid)
{
  if (PRIVATE(this)->callbacks == NULL)
    return;
  const int numCallbacks = PRIVATE(this)->callbacks->getLength();
  int i;
  for (i = 0; i < numCallbacks; i++) {
    So@Gui@MenuSelectionCallback * cb = (So@Gui@MenuSelectionCallback *)
      (*PRIVATE(this)->callbacks)[i];
    (*cb)(itemid, (*PRIVATE(this)->userdata)[i]);
  }
} // invokeMenuSelection()

// *************************************************************************

