    // @(#)root/gui:$Name:  $:$Id: TGClient.cxx,v 1.36 2004/06/17 10:54:05 rdm Exp $
    // Author: Fons Rademakers   27/12/97

    /*************************************************************************
     * 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.             *
     *************************************************************************/
    /**************************************************************************

        This source is based on Xclass95, a Win95-looking GUI toolkit.
        Copyright (C) 1996, 1997 David Barth, Ricky Ralston, Hector Peraza.

        Xclass95 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.

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

    //////////////////////////////////////////////////////////////////////////
    //                                                                      //
    // TGClient                                                             //
    //                                                                      //
    // Window client. In client server windowing systems, like X11 this     //
    // class is used to make the initial connection to the window server.   //
    //                                                                      //
    //////////////////////////////////////////////////////////////////////////

    #ifdef HAVE_CONFIG
    #include "config.h"
    #endif

    #include "TGClient.h"
    #include "TROOT.h"
    #include "TSystem.h"
    #include "TEnv.h"
    #include "THashList.h"
    #include "TSysEvtHandler.h"
    #include "TVirtualX.h"
    #include "TGWindow.h"
    #include "TGResourcePool.h"
    #include "TGGC.h"
    #include "TGFont.h"
    #include "TGMimeTypes.h"
    #include "TGFrame.h"

    // Global pointer to the TGClient object
    TGClient *gClient;

    //----- Graphics Input handler -------------------------------------------------
    //______________________________________________________________________________
    class TGInputHandler : public TFileHandler {
    private:
       TGClient  *fClient;
    public:
       TGInputHandler(TGClient *c, Int_t fd) : TFileHandler(fd, 1) { fClient = c; }
       Bool_t Notify();
       // Important: don't override ReadNotify()
    };

    //______________________________________________________________________________
    Bool_t TGInputHandler::Notify()
    {
       return fClient->HandleInput();
    }

    ClassImp(TGClient)

    //______________________________________________________________________________
     TGClient::TGClient(const char *dpyName)
    {
       // Create a connection with the display sever on host DpyName and setup
       // the complete GUI system, i.e., graphics contexts, fonts, etc. for all
       // widgets.

       fRoot         = 0;
       fPicturePool  = 0;
       fMimeTypeList = 0;
       fWlist        = 0;
       fPlist        = 0;
       fUWHandlers   = 0;
       if (gClient) {
          Error("TGClient", "only one instance of TGClient allowed");
          return;
       }

       // Set DISPLAY based on utmp (only if DISPLAY is not yet set).
       gSystem->SetDisplay();

       // Open the connection to the display
       if ((fXfd = gVirtualX->OpenDisplay(dpyName)) < 0) {
          Error("TGClient", "can't open display \"%s\", switching to batch mode...",
                gVirtualX->DisplayName(dpyName));
          MakeZombie();
          return;
       }
       if (fXfd >= 0) {
          TGInputHandler *xi = new TGInputHandler(this, fXfd);
          if (fXfd) gSystem->AddFileHandler(xi);
          // X11 events are handled via gXDisplay->Notify() in
          // TUnixSystem::DispatchOneEvent(). When no events available we wait for
          // events on all TFileHandlers including this one via a select() call.
          // However, X11 events are always handled via gXDisplay->Notify() and not
          // via the ReadNotify() (therefore TGInputHandler should not override
          // TFileHandler::ReadNotify()).
          gXDisplay = xi;
       }

       // Initialize internal window list. Use a THashList for fast
       // finding of windows based on window id (see GetWindowById()).

       fWlist = new THashList(200);
       fPlist = new TList;
       fUWHandlers = 0;

       // Create root window

       fDefaultRoot = fRoot = new TGFrame(this, gVirtualX->GetDefaultRootWindow());

       // Setup some atoms (defined in TVirtualX)...

       gWM_DELETE_WINDOW = gVirtualX->InternAtom("WM_DELETE_WINDOW", kFALSE);
       gMOTIF_WM_HINTS   = gVirtualX->InternAtom("_MOTIF_WM_HINTS", kFALSE);
       gROOT_MESSAGE     = gVirtualX->InternAtom("_ROOT_MESSAGE", kFALSE);

       // Create the graphics event handler, an object for the root window,
       // a picture pool, mimetype list, etc...

       fGlobalNeedRedraw = kFALSE;
       fForceRedraw      = kFALSE;
       fWaitForWindow    = kNone;

       fResourcePool = new TGResourcePool(this);

       fPicturePool     = fResourcePool->GetPicturePool();
       fGCPool          = fResourcePool->GetGCPool();
       fFontPool        = fResourcePool->GetFontPool();

       fMimeTypeList    = fResourcePool->GetMimeTypes();
       fDefaultColormap = fResourcePool->GetDefaultColormap();

       // Set some color defaults...

       fWhite        = fResourcePool->GetWhiteColor();
       fBlack        = fResourcePool->GetBlackColor();
       fBackColor    = fResourcePool->GetFrameBgndColor();
       fForeColor    = fResourcePool->GetFrameFgndColor();
       fHilite       = GetHilite(fBackColor);
       fShadow       = GetShadow(fBackColor);
       fSelForeColor = fResourcePool->GetSelectedFgndColor();
       fSelBackColor = fResourcePool->GetSelectedBgndColor();

       gClient = this;
    }

    //______________________________________________________________________________
     const TGWindow *TGClient::GetRoot() const
    {
       // Returns current root (i.e. base) window. By changing the root
       // window one can change the window hierarchy, e.g. a top level
       // frame (TGMainFrame) can be embedded in another window.

       return fRoot;
    }

    //______________________________________________________________________________
     const TGWindow *TGClient::GetDefaultRoot() const
    {
       // Returns the root (i.e. desktop) window. Should only be used as parent
       // for frames that will never be embedded, like popups, message boxes,
       // etc. (like TGToolTips, TGMessageBox, etc.).

       return fDefaultRoot;
    }

    //______________________________________________________________________________
     void TGClient::SetRoot(TGWindow *root)
    {
       // Sets the current root (i.e. base) window. By changing the root
       // window one can change the window hierarchy, e.g. a top level
       // frame (TGMainFrame) can be embedded in another window.

       fRoot = root ? root : fDefaultRoot;
    }

    //______________________________________________________________________________
     const TGPicture *TGClient::GetPicture(const char *name)
    {
       // Get picture from the picture pool. Picture must be freed using
       // TGClient::FreePicture(). If picture is not found 0 is returned.

       return fPicturePool->GetPicture(name);
    }

    //______________________________________________________________________________
     const TGPicture *TGClient::GetPicture(const char *name,
                                          UInt_t new_width, UInt_t new_height)
    {
       // Get picture with specified size from pool (picture will be scaled if
       // necessary). Picture must be freed using TGClient::FreePicture(). If
       // picture is not found 0 is returned.

       return fPicturePool->GetPicture(name, new_width, new_height);
    }

    //______________________________________________________________________________
     void TGClient::FreePicture(const TGPicture *pic)
    {
       // Free picture resource.

       if (pic) fPicturePool->FreePicture(pic);
    }

    //______________________________________________________________________________
     TGGC *TGClient::GetGC(GCValues_t *values, Bool_t rw)
    {
       // Get graphics context from the gc pool. Context must be freed via
       // TGClient::FreeGC(). If rw is true a new read/write-able GC
       // is returned, otherwise a shared read-only context is returned.
       // For historical reasons it is also possible to create directly a
       // TGGC object, but it is advised to use this new interface only.

       return fGCPool->GetGC(values, rw);
    }

    //______________________________________________________________________________
     void TGClient::FreeGC(const TGGC *gc)
    {
       // Free a graphics context.

       fGCPool->FreeGC(gc);
    }

    //______________________________________________________________________________
     void TGClient::FreeGC(GContext_t gc)
    {
       // Free a graphics context.

       fGCPool->FreeGC(gc);
    }

    //______________________________________________________________________________
     TGFont *TGClient::GetFont(const char *font, Bool_t fixedDefault)
    {
       // Get a font from the font pool. Fonts must be freed via
       // TGClient::FreeFont(). Returns 0 in case of error or if font
       // does not exist. If fixedDefault is false the "fixed" font
       // will not be substituted as fallback when the asked for font
       // does not exist.

       return fFontPool->GetFont(font, fixedDefault);
    }

    //______________________________________________________________________________
     TGFont *TGClient::GetFont(const TGFont *font)
    {
       // Get again specified font. Will increase its usage count.

       return fFontPool->GetFont(font);
    }

    //______________________________________________________________________________
     void TGClient::FreeFont(const TGFont *font)
    {
       // Free a font.

       fFontPool->FreeFont(font);
    }

    //______________________________________________________________________________
     void TGClient::NeedRedraw(TGWindow *w)
    {
       // Set redraw flags.

       w->fNeedRedraw = kTRUE;
       fGlobalNeedRedraw = kTRUE;
    }

    //______________________________________________________________________________
     Bool_t TGClient::GetColorByName(const char *name, Pixel_t &pixel) const
    {
       // Get a color by name. If color is found return kTRUE and pixel is
       // set to the color's pixel value, kFALSE otherwise.

       ColorStruct_t      color;
       WindowAttributes_t attributes;
       Bool_t             status = kTRUE;

       gVirtualX->GetWindowAttributes(fRoot->GetId(), attributes);
       color.fPixel = 0;
       if (!gVirtualX->ParseColor(attributes.fColormap, name, color)) {
          Error("GetColorByName", "couldn't parse color %s", name);
          status = kFALSE;
       } else if (!gVirtualX->AllocColor(attributes.fColormap, color)) {
          Warning("GetColorByName", "couldn't retrieve color %s.\n"
                  "Please close any other application, like netscape, "
                  "that might exhaust\nthe colormap and start ROOT again", name);
          status = kFALSE;
       }

       pixel = color.fPixel;

       return status;
    }

    //______________________________________________________________________________
     FontStruct_t TGClient::GetFontByName(const char *name, Bool_t fixedDefault) const
    {
       // Get a font by name. If font is not found, fixed font is returned,
       // if fixed font also does not exist return 0 and print error.
       // The loaded font needs to be freed using TVirtualX::DeleteFont().
       // If fixedDefault is false the "fixed" font will not be substituted
       // as fallback when the asked for font does not exist.

       FontStruct_t font = gVirtualX->LoadQueryFont(name);

       if (!font && fixedDefault) {
          font = gVirtualX->LoadQueryFont("fixed");
          if (font)
             Warning("GetFontByName", "couldn't retrieve font %s, using \"fixed\"", name);
       }
       if (!font) {
          if (fixedDefault)
             Error("GetFontByName", "couldn't retrieve font %s nor backup font \"fixed\"", name);
          else
             Warning("GetFontByName", "couldn't retrieve font %s", name);
       }

       return font;
    }

    //______________________________________________________________________________
     Pixel_t TGClient::GetHilite(Pixel_t base_color) const
    {
       // Return pixel value of hilite color based on base_color.

       ColorStruct_t      color, white_p;
       WindowAttributes_t attributes;

       gVirtualX->GetWindowAttributes(fRoot->GetId(), attributes);

       color.fPixel = base_color;
       gVirtualX->QueryColor(attributes.fColormap, color);

       GetColorByName("white", white_p.fPixel);
       gVirtualX->QueryColor(attributes.fColormap, white_p);

       color.fRed   = TMath::Max((UShort_t)(white_p.fRed/5),   color.fRed);
       color.fGreen = TMath::Max((UShort_t)(white_p.fGreen/5), color.fGreen);
       color.fBlue  = TMath::Max((UShort_t)(white_p.fBlue/5),  color.fBlue);

       color.fRed   = (UShort_t)TMath::Min((Int_t)white_p.fRed,   (Int_t)(color.fRed*140)/100);
       color.fGreen = (UShort_t)TMath::Min((Int_t)white_p.fGreen, (Int_t)(color.fGreen*140)/100);
       color.fBlue  = (UShort_t)TMath::Min((Int_t)white_p.fBlue,  (Int_t)(color.fBlue*140)/100);

       if (!gVirtualX->AllocColor(attributes.fColormap, color))
          Error("GetHilite", "couldn't allocate hilight color");

       return color.fPixel;
    }

    //______________________________________________________________________________
     Pixel_t TGClient::GetShadow(Pixel_t base_color) const
    {
       // Return pixel value of shadow color based on base_color.
       // Shadow is 60% of base_color intensity.

       ColorStruct_t      color;
       WindowAttributes_t attributes;

       gVirtualX->GetWindowAttributes(fRoot->GetId(), attributes);

       color.fPixel = base_color;
       gVirtualX->QueryColor(attributes.fColormap, color);

       color.fRed   = (UShort_t)((color.fRed*60)/100);
       color.fGreen = (UShort_t)((color.fGreen*60)/100);
       color.fBlue  = (UShort_t)((color.fBlue*60)/100);

       if (!gVirtualX->AllocColor(attributes.fColormap, color))
          Error("GetShadow", "couldn't allocate shadow color");

      return color.fPixel;
    }

    //______________________________________________________________________________
     void TGClient::FreeColor(Pixel_t color) const
    {
       // Free color.

       gVirtualX->FreeColor(fDefaultColormap, color);
    }

    //______________________________________________________________________________
     void TGClient::RegisterWindow(TGWindow *w)
    {
       // Add a TGWindow to the clients list of windows.

       fWlist->Add(w);
    }

    //______________________________________________________________________________
     void TGClient::UnregisterWindow(TGWindow *w)
    {
       // Remove a TGWindow from the list of windows.

       fWlist->Remove(w);
    }

    //______________________________________________________________________________
     void TGClient::RegisterPopup(TGWindow *w)
    {
       // Add a popup menu to the list of popups. This list is used to pass
       // events to popup menus that are popped up over a transient window which
       // is waited for (see WaitFor()).

       fPlist->Add(w);
    }

    //______________________________________________________________________________
     void TGClient::UnregisterPopup(TGWindow *w)
    {
       // Remove a popup menu from the list of popups.

       fPlist->Remove(w);
    }

    //______________________________________________________________________________
     void TGClient::AddUnknownWindowHandler(TGUnknownWindowHandler *h)
    {
       // Add handler for unknown (i.e. unregistered) windows.

       if (!fUWHandlers)
          fUWHandlers = new TList;

       fUWHandlers->Add(h);
    }

    //______________________________________________________________________________
     void TGClient::RemoveUnknownWindowHandler(TGUnknownWindowHandler *h)
    {
       // Remove handler for unknown (i.e. unregistered) windows.

       fUWHandlers->Remove(h);
    }

    //______________________________________________________________________________
     TGWindow *TGClient::GetWindowById(Window_t wid) const
    {
       // Find a TGWindow via its handle. If window is not found return 0.

       TGWindow  wt(wid);

       return (TGWindow *) fWlist->FindObject(&wt);
    }

    //______________________________________________________________________________
     TGClient::~TGClient()
    {
       // Closing down client: cleanup and close X connection.

       if (IsZombie())
          return;

       if (fWlist)
          fWlist->Delete("slow");
       delete fWlist;
       delete fPlist;
       if (fUWHandlers)
          fUWHandlers->Delete();
       delete fUWHandlers;
       delete fResourcePool;

       gVirtualX->CloseDisplay(); // this should do a cleanup of the remaining
                                  // X allocated objects...
    }

    //______________________________________________________________________________
     Bool_t TGClient::ProcessOneEvent()
    {
       // Process one event. This method should only be called when there is
       // a GUI event ready to be processed. If event has been processed
       // kTRUE is returned. If processing of a specific event type for a specific
       // window was requested kFALSE is returned when specific event has been
       // processed, kTRUE otherwise. If no more pending events return kFALSE.

       Event_t event;

       if (!fRoot) return kFALSE;
       if (gVirtualX->EventsPending()) {
          gVirtualX->NextEvent(event);
          if (fWaitForWindow == kNone) {
             HandleEvent(&event);
             if (fForceRedraw)
                DoRedraw();
             return kTRUE;
          } else {
             HandleMaskEvent(&event, fWaitForWindow);
             if ((event.fType == fWaitForEvent) && (event.fWindow == fWaitForWindow))
                fWaitForWindow = kNone;
             if (fForceRedraw)
                DoRedraw();
             return kTRUE;
          }
       }

       // if nothing else to do redraw windows that need redrawing
       if (DoRedraw()) return kTRUE;

       return kFALSE;
    }

    //______________________________________________________________________________
     Bool_t TGClient::HandleInput()
    {
       // Handles input from the display server. Returns kTRUE if one or more
       // events have been processed, kFALSE otherwise.

       Bool_t handledevent = kFALSE;

       while (ProcessOneEvent())
          handledevent = kTRUE;
       return handledevent;
    }

    //______________________________________________________________________________
     void TGClient::WaitFor(TGWindow *w)
    {
       // Wait for window to be destroyed.

       Window_t wsave    = fWaitForWindow;
       EGEventType esave = fWaitForEvent;

       fWaitForWindow = w->GetId();
       fWaitForEvent  = kDestroyNotify;

       while (fWaitForWindow != kNone)
          gSystem->InnerLoop();

       fWaitForWindow = wsave;
       fWaitForEvent  = esave;
    }

    //______________________________________________________________________________
     void TGClient::WaitForUnmap(TGWindow *w)
    {
       // Wait for window to be unmapped.

       Window_t wsave    = fWaitForWindow;
       EGEventType esave = fWaitForEvent;

       fWaitForWindow = w->GetId();
       fWaitForEvent  = kUnmapNotify;

       while (fWaitForWindow != kNone)
          gSystem->InnerLoop();

       fWaitForWindow = wsave;
       fWaitForEvent  = esave;
    }

    //______________________________________________________________________________
     Bool_t TGClient::ProcessEventsFor(TGWindow *w)
    {
       // Like gSystem->ProcessEvents() but then only allow events for w to
       // be processed. For example to interrupt the processing and destroy
       // the window, call gROOT->SetInterrupt() before destroying the window.

       Window_t wsave    = fWaitForWindow;
       EGEventType esave = fWaitForEvent;

       fWaitForWindow = w->GetId();
       fWaitForEvent  = kDestroyNotify;

       Bool_t intr = gSystem->ProcessEvents();

       fWaitForWindow = wsave;
       fWaitForEvent  = esave;

       return intr;
    }

    //______________________________________________________________________________
     Bool_t TGClient::DoRedraw()
    {
       // Redraw all windows that need redrawing. Returns kFALSE if no redraw
       // was needed, kTRUE otherwise.
       // Only redraw the application's windows when the event queue
       // does not contain expose event anymore.

       if (!fGlobalNeedRedraw) return kFALSE;

       TGWindow *w;
       TObjLink *lnk = fWlist->FirstLink();
       while (lnk) {
          w = (TGWindow *) lnk->GetObject();
          if (w->fNeedRedraw) {
             w->DoRedraw();
             w->fNeedRedraw = kFALSE;
          }
          lnk = lnk->Next();
       }

       fGlobalNeedRedraw = kFALSE;
       fForceRedraw      = kFALSE;

       return kTRUE;
    }

    //______________________________________________________________________________
     Bool_t TGClient::HandleEvent(Event_t *event)
    {
       // Handle a GUI event.

       TGWindow *w;

       // Find window where event happened
       if ((w = GetWindowById(event->fWindow)) == 0) {
          if (fUWHandlers && fUWHandlers->GetSize() > 0) {
             TGUnknownWindowHandler *unkwh;
             TListIter it(fUWHandlers);
             while ((unkwh = (TGUnknownWindowHandler*)it.Next())) {
                if (unkwh->HandleEvent(event))
                   return kTRUE;
             }
          }
          //Warning("HandleEvent", "unknown window %ld not handled\n",
          //        event->fWindow);
          return kFALSE;
       }

       // and let it handle the event
       w->HandleEvent(event);

       return kTRUE;
    }

    //______________________________________________________________________________
     Bool_t TGClient::HandleMaskEvent(Event_t *event, Window_t wid)
    {
       // Handle masked events only if window wid is the window for which the
       // event was reported or if wid is a parent of the event window. The not
       // masked event are handled directly. The masked events are:
       // kButtonPress, kButtonRelease, kKeyPress, kKeyRelease, kEnterNotify,
       // kLeaveNotify, kMotionNotify.

       TGWindow *w, *ptr, *pop;

       if ((w = GetWindowById(event->fWindow)) == 0) return kFALSE;

       // This breaks class member protection, but TGClient is a friend of
       // TGWindow and _should_ know what to do and what *not* to do...

       for (ptr = w; ptr->fParent != 0; ptr = (TGWindow *) ptr->fParent) {
          if ((ptr->fId == wid) ||
              ((event->fType != kButtonPress) &&
               (event->fType != kButtonRelease) &&
               (event->fType != kGKeyPress) &&
               (event->fType != kKeyRelease) &&
               (event->fType != kEnterNotify) &&
               (event->fType != kLeaveNotify) &&
               (event->fType != kMotionNotify))) {
             w->HandleEvent(event);
             return kTRUE;
          }
       }

       // check if this is a popup menu
       TIter next(fPlist);
       while ((pop = (TGWindow *) next())) {
          for (ptr = w; ptr->fParent != 0; ptr = (TGWindow *) ptr->fParent) {
             if ((ptr->fId == pop->fId) &&
                 ((event->fType == kButtonPress) ||
                  (event->fType == kButtonRelease) ||
                  (event->fType == kGKeyPress) ||
                  (event->fType == kKeyRelease) ||
                  (event->fType == kEnterNotify) ||
                  (event->fType == kLeaveNotify) ||
                  (event->fType == kMotionNotify))) {
                w->HandleEvent(event);
                return kTRUE;
             }
          }
       }

       if (event->fType == kButtonPress || event->fType == kGKeyPress)
          gVirtualX->Bell(0);

       return kFALSE;
    }

    //______________________________________________________________________________
     void TGClient::ProcessLine(TString cmd, Long_t msg, Long_t parm1, Long_t parm2)
    {
       // Execute string "cmd" via the interpreter. Before executing replace
       // in the command string the token $MSG, $PARM1 and $PARM2 by msg,
       // parm1 and parm2, respectively. The function in cmd string must accept
       // these as longs.

       if (cmd.IsNull()) return;

       char s[32];

       sprintf(s, "%ld", msg);
       cmd.ReplaceAll("$MSG", s);

       sprintf(s, "%ld", parm1);
       cmd.ReplaceAll("$PARM1", s);

       sprintf(s, "%ld", parm2);
       cmd.ReplaceAll("$PARM2", s);

       gROOT->ProcessLine(cmd.Data());
    }