// @(#)root/base:$Id$
// Author: Nenad Buncic   08/02/96

/*************************************************************************
 * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

/** \class TContextMenu
\ingroup Base

This class provides an interface to  context sensitive popup menus.
These menus pop up when the user hits the right mouse button, and
are destroyed when the menu pops downs.

Context Menus are automatically generated by ROOT using the
following convention: if the string `// *MENU*` is found in the
comment field of a member function. This function will be added to
the list of items in the menu.

The picture below shows a canvas with a pop-up menu.

\image html base_hsummenu.png

The picture below shows a canvas with a pop-up menu and a dialog box.

\image html base_hsumdialog.png
*/

// silence warning about some cast operations
#if defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ >= 4 && ((__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ >= 1) || (__GNUC_MINOR__ >= 3)))) && !__INTEL_COMPILER
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif

#include "TROOT.h"
#include "TContextMenu.h"
#include "TContextMenuImp.h"
#include "TVirtualPad.h"
#include "TGuiFactory.h"
#include "TMethod.h"
#include "TMethodArg.h"
#include "TObjArray.h"
#include "TObjString.h"
#include "TToggle.h"
#include "TClassMenuItem.h"
#include "TBrowser.h"
#include "TClass.h"
#include "TObjectSpy.h"

ClassImp(TContextMenu);


////////////////////////////////////////////////////////////////////////////////
/// Create a context menu.

TContextMenu::TContextMenu(const char *name, const char *title)
             : TNamed(name, title)
{
   fSelectedObject   = nullptr;
   fCalledObject     = nullptr;
   fSelectedMethod   = nullptr;
   fBrowser          = nullptr;
   fSelectedPad      = nullptr;
   fSelectedCanvas   = nullptr;
   fSelectedMenuItem = nullptr;

   fContextMenuImp = gGuiFactory->CreateContextMenuImp(this, name, title);
}

////////////////////////////////////////////////////////////////////////////////
/// Destroy a context menu.

TContextMenu::~TContextMenu()
{
   delete fContextMenuImp;

   fSelectedMethod   = nullptr;
   fCalledObject     = nullptr;
   fSelectedObject   = nullptr;
   fSelectedMenuItem = nullptr;
   fContextMenuImp   = nullptr;
}

////////////////////////////////////////////////////////////////////////////////
/// Action to be performed when this menu item is selected.
/// If the selected method requires arguments we popup an
/// automatically generated dialog, otherwise the method is
/// directly executed.

void TContextMenu::Action(TObject *object, TMethod *method)
{
   if (method) {
      SetMethod( method );
      SetSelectedMenuItem(nullptr);
      SetCalledObject(object);

      if (method->GetListOfMethodArgs()->First())
         fContextMenuImp->Dialog(object, method);
      else {
         Execute(object, method, "");
      }
   }

   if (fBrowser) fBrowser->Refresh();
}

////////////////////////////////////////////////////////////////////////////////
/// Action to be performed when this menu item is selected.
/// If the selected method requires arguments we popup an
/// automatically generated dialog, otherwise the method is
/// directly executed.

void TContextMenu::Action(TClassMenuItem *menuitem)
{
   TObject* object;
   TMethod* method = nullptr;

   SetSelectedMenuItem( menuitem );

   // Get the object to be called
   if (menuitem->IsCallSelf()) object=fSelectedObject;
   else object=menuitem->GetCalledObject();

   if (object) {
      // If object deleted, remove from popup and return
      if (ROOT::Detail::HasBeenDeleted(object)) {
         menuitem->SetType(TClassMenuItem::kPopupSeparator);
         menuitem->SetCall(nullptr, "");
         return;
      }

      method = object->IsA()->GetMethodWithPrototype(menuitem->GetFunctionName(),menuitem->GetArgs());

   }

   // calling object, call the method directly
   if (object) {
      if (method) {
         SetMethod(method);
         SetCalledObject(object);

         if ((method->GetListOfMethodArgs()->First()
                            && menuitem->GetSelfObjectPos() < 0 ) ||
              method->GetListOfMethodArgs()->GetSize() > 1)
            fContextMenuImp->Dialog(object, method);
         else {
            if (menuitem->GetSelfObjectPos() < 0) {
#ifndef WIN32
               Execute(object, method, "");
#else
               // It is a workaround of the "Dead lock under Windows
               TString cmd;
               cmd.Form("((TContextMenu *)0x%zx)->Execute((TObject *)0x%zx,"
                        "(TMethod *)0x%zx,\"\");",
                        (size_t)this,(size_t)object,(size_t)method);
               //Printf("%s", cmd.Data());
               gROOT->ProcessLine(cmd.Data());
               //Execute( object, method, (TObjArray *)NULL );
#endif
            } else {
#ifndef WIN32
               Execute(object, method, TString::Format("(TObject*)0x%zx",(size_t)fSelectedObject).Data());
#else
               // It is a workaround of the "Dead lock under Windows
               TString cmd;
               cmd.Form("((TContextMenu *)0x%zx)->Execute((TObject *)0x%zx,"
                        "(TMethod *)0x%zx,(TObject*)0x%zx);",
                        (size_t)this,(size_t)object,(size_t)method,
                        (size_t)fSelectedObject);
               //Printf("%s", cmd.Data());
               gROOT->ProcessLine(cmd.Data());
               //Execute( object, method, (TObjArray *)NULL );
#endif
            }
         }
      }

   } else {
      // Calling a standalone global function
      TFunction* function = gROOT->GetGlobalFunctionWithPrototype(
                                 menuitem->GetFunctionName());
                                 //menuitem->GetArgs());
      if (function) {
         SetMethod(function);
         SetCalledObject(nullptr);
         if ( (function->GetNargs() && menuitem->GetSelfObjectPos() < 0) ||
               function->GetNargs() > 1) {
            fContextMenuImp->Dialog(nullptr, function);
         } else {
            TString cmd;
            if (menuitem->GetSelfObjectPos() < 0) {
               cmd.Form("%s();", menuitem->GetFunctionName());
            } else {
              cmd.Form("%s((TObject*)0x%zx);",
                     menuitem->GetFunctionName(), (size_t)fSelectedObject);
            }
            gROOT->ProcessLine(cmd.Data());
         }
      }
   }

   if (fBrowser) fBrowser->Refresh();
}

////////////////////////////////////////////////////////////////////////////////
/// Action to be performed when this toggle menu item is selected.

void TContextMenu::Action(TObject *object, TToggle *toggle)
{
   if (object && toggle) {
      TObjectSpy savePad;

      gROOT->SetSelectedPrimitive(object);
      if (fSelectedPad && gPad) {
         savePad.SetObject(gPad);
         fSelectedPad->cd();
      }
      TObjectRefSpy fsp((TObject*&) fSelectedPad);
      TObjectRefSpy fsc((TObject*&) fSelectedCanvas);

      gROOT->SetFromPopUp(kTRUE);
      toggle->Toggle();
      if (fSelectedCanvas && fSelectedCanvas->GetPadSave())
         fSelectedCanvas->GetPadSave()->Modified();
      if (fSelectedPad)
         fSelectedPad->Modified();
      gROOT->SetFromPopUp(kFALSE);

      if (savePad.GetObject())
         ((TVirtualPad*)savePad.GetObject())->cd();

      if (fSelectedCanvas) {
         fSelectedCanvas->Update();
         if (fSelectedCanvas->GetPadSave())
            fSelectedCanvas->GetPadSave()->Update();
      }
   }

   if (fBrowser) fBrowser->Refresh();
}

////////////////////////////////////////////////////////////////////////////////
/// Create string describing argument (for use in dialog box).

const char *TContextMenu::CreateArgumentTitle(TMethodArg *argument)
{
   static TString argTitle;

   if (argument) {
      argTitle.Form("(%s)  %s", argument->GetTitle(), argument->GetName());
      if (argument->GetDefault() && *(argument->GetDefault())) {
         argTitle += "  [default: ";
         argTitle += argument->GetDefault();
         argTitle += "]";
      }
   } else
      argTitle.Clear();

   return argTitle.Data();
}

////////////////////////////////////////////////////////////////////////////////
/// Create title for dialog box retrieving argument values.

const char *TContextMenu::CreateDialogTitle(TObject *object, TFunction *method)
{
   static TString methodTitle;

   if (object && method)
      methodTitle.Form("%s::%s", object->ClassName(), method->GetName());
   else if (!object && method)
      methodTitle.Form("%s", method->GetName());
   else
      methodTitle.Clear();

   return methodTitle.Data();
}

////////////////////////////////////////////////////////////////////////////////
/// Create title for popup menu.

const char *TContextMenu::CreatePopupTitle(TObject *object)
{
   static TString popupTitle;

   if (object) {
      const char* clname = object->IsA()->GetContextMenuTitle();
      if (!clname[0])
         clname = object->ClassName();

      if (!*(object->GetName()) || !strcmp(object->GetName(), object->ClassName())) {
         popupTitle.Form("  %s  ", clname);
      } else {
         popupTitle.Form("  %s::%s  ", clname, object->GetName());
      }
      if (popupTitle.Length() > 60) {
         popupTitle.Remove(60);
         popupTitle += "...";
      }
   } else
      popupTitle.Clear();

   return popupTitle.Data();
}

////////////////////////////////////////////////////////////////////////////////
/// Display popup.

void TContextMenu::DisplayPopUp(Int_t x, Int_t y)
{
   if (fContextMenuImp)
      fContextMenuImp->DisplayPopup(x, y);
}


////////////////////////////////////////////////////////////////////////////////
/// Execute method with specified arguments for specified object.

void TContextMenu::Execute(TObject *object, TFunction *method, const char *params)
{
   if (method) {
      TObjectSpy savePad;

      gROOT->SetSelectedPrimitive(object);
      if (fSelectedPad && gPad) {
         savePad.SetObject(gPad);
         fSelectedPad->cd();
      }
      TObjectRefSpy fsp((TObject*&) fSelectedPad);
      TObjectRefSpy fsc((TObject*&) fSelectedCanvas);

      gROOT->SetFromPopUp(kTRUE);
      if (object) {
         object->Execute(method->GetName(), params);
      } else {
         gROOT->ProcessLine(TString::Format("%s(%s);", method->GetName(),params).Data());
      }
      if (fSelectedCanvas && fSelectedCanvas->GetPadSave())
         fSelectedCanvas->GetPadSave()->Modified();
      if (fSelectedPad)
         fSelectedPad->Modified();
      gROOT->SetFromPopUp(kFALSE);

      if (savePad.GetObject())
         ((TVirtualPad*)savePad.GetObject())->cd();

      if (fSelectedCanvas) {
         fSelectedCanvas->Update();
         if (fSelectedCanvas->GetPadSave())
            fSelectedCanvas->GetPadSave()->Update();
      }
   }

   if (fBrowser) fBrowser->Refresh();
}

////////////////////////////////////////////////////////////////////////////////
/// Execute method with specified arguments for specified object.

void TContextMenu::Execute(TObject *object, TFunction *method, TObjArray *params)
{
   if (method) {
      TObjectSpy savePad;

      gROOT->SetSelectedPrimitive(object);
      if (fSelectedPad && gPad) {
         savePad.SetObject(gPad);
         fSelectedPad->cd();
      }
      TObjectRefSpy fsp((TObject*&) fSelectedPad);
      TObjectRefSpy fsc((TObject*&) fSelectedCanvas);

      gROOT->SetFromPopUp(kTRUE);
      if (object) {
         object->Execute((TMethod*)method, params);
      } else {
         TString args;
         TIter next(params);
         TObjString *s;
         while ((s = (TObjString*) next())) {
            if (!args.IsNull()) args += ",";
            args += s->String();
         }
         gROOT->ProcessLine(TString::Format("%s(%s);", method->GetName(), args.Data()).Data());
      }
      if (fSelectedCanvas && fSelectedCanvas->GetPadSave())
         fSelectedCanvas->GetPadSave()->Modified();
      if (fSelectedPad)
         fSelectedPad->Modified();
      gROOT->SetFromPopUp(kFALSE);

      if (savePad.GetObject())
         ((TVirtualPad*)savePad.GetObject())->cd();

      if (fSelectedCanvas) {
         fSelectedCanvas->Update();
         if (fSelectedCanvas->GetPadSave())
            fSelectedCanvas->GetPadSave()->Update();
      }
   }
   if (fBrowser) fBrowser->Refresh();
}

////////////////////////////////////////////////////////////////////////////////
/// Popup context menu at given location in canvas c and pad p for selected
/// object.

void TContextMenu::Popup(Int_t x, Int_t y, TObject *obj, TVirtualPad *c, TVirtualPad *p)
{
   SetBrowser(nullptr);
   SetObject(obj);
   SetCanvas(c);
   SetPad(p);

   DisplayPopUp(x,y);
}

////////////////////////////////////////////////////////////////////////////////
/// Popup context menu at given location in browser b for selected object.

void TContextMenu::Popup(Int_t x, Int_t y, TObject *obj, TBrowser *b)
{
   SetBrowser(b);
   SetObject(obj);
   SetCanvas(nullptr);
   SetPad(nullptr);

   DisplayPopUp(x,y);
}
