/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: sitetext.cpp,v 1.1.1.1.42.5 2004/09/17 23:05:21 nhart Exp $
 *
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 *
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 *
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 *
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 *
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 *
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 *
 * Contributor(s):
 *
 * ***** END LICENSE BLOCK ***** */

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Intrinsic.h>

#include "hxcom.h"
#include "hxtypes.h"
#include "hxwintyp.h"
#include "hxmap.h"
#include "hxslist.h"

#include "ihxpckts.h"
#include "hxwin.h"
#include "hxengin.h"

#include "hxsite2.h"
#include "chxxtype.h"

#include "hxvctrl.h"
#include "hxvsurf.h"
#include "surface.h"
#include "vidosurf.h"
#include "chxpckts.h"
#include "hxevent.h"

#include "sitemgr.h"
#include "sitetext.h"

//#include "pnmm.h" ??

#define DEF_TEXT_HEIGHT		12
#define POINTS_TO_PIXELS	1.5
#define DEF_TEXT_GUTTER		1.2
#define DEF_STAT_HEIGHT		(int)((double)DEF_TEXT_HEIGHT * POINTS_TO_PIXELS * DEF_TEXT_GUTTER)
#define DEF_HORZ_GUTTER		4
#define ELIPSIS			"..."
#define ELIPSIS_LEN		3
#define DEFAULT_X_FONT          "-adobe-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*"
#define FALLBACK_X_FONT         "variable"

// fwd declares
typedef ULONG32 COLORTYPE;



CHXSiteStatusText::CHXSiteStatusText()
   : m_lRefCount(0)
   , m_pSite(NULL)
   , m_pParentSite(NULL)
   , m_rgbBkgndColor(0x00008000)
   , m_rgbTextColor(0x0000FF00)
   , m_colormap(0)
   , m_display(0)
   , m_window(0)
   , m_statusfont(NULL)
{
   memset(&m_statusPos, 0, sizeof(HXxPoint));
   memset(&m_statusSize, 0, sizeof(HXxSize));
}

CHXSiteStatusText::~CHXSiteStatusText()
{
   Destroy(); // make sure we cleanup
}

HX_RESULT CHXSiteStatusText::Create(IHXSite* pSite)
{
   IHXSite*	pChildSite	= NULL;
   HX_RESULT 	hr		= HXR_FAIL;

   HX_ASSERT(pSite);
   HX_ASSERT(!m_pSite);

   // keep a ref while we use the parent site
   pSite->AddRef();

   if (pSite->CreateChild(pChildSite) == HXR_OK)
   {
      if (pChildSite->AttachUser(this) == HXR_OK)
      {
         m_pParentSite = pSite;
         if (m_StatusText.IsEmpty())
         {
            Hide(); // keep hidden until we have status text
         }
         else
         {
            Show();
         }
         UpdatePosition();
         hr = HXR_OK;
      }
      HX_RELEASE(pChildSite);
   }

   // release if we failed
   if (hr != HXR_OK)
   {
      HX_RELEASE(pSite);
   }

   return hr;
}


HX_RESULT CHXSiteStatusText::Destroy()
{
   if (m_pSite)
   {
      if (m_pParentSite)
      {
         m_pParentSite->DestroyChild(m_pSite);
      }
      // DetachUser will call DetachSite, which will release m_pSite
      m_pSite->DetachUser();
      m_pSite == NULL;
   }

   HX_RELEASE(m_pParentSite);

   return HXR_OK;
}

HX_RESULT CHXSiteStatusText::Show()
{
   IHXSite2* pSite2 = NULL;

   if (GetIHXSite2(pSite2) == HXR_OK)
   {
      pSite2->ShowSite(TRUE);
      HX_RELEASE(pSite2);
   }

   BringToTop();

   return HXR_OK;
}


HX_RESULT CHXSiteStatusText::Hide()
{
   IHXSite2* pSite2 = NULL;

   if (GetIHXSite2(pSite2) == HXR_OK)
   {
      pSite2->ShowSite(FALSE);
      HX_RELEASE(pSite2);
   }

   return HXR_OK;
}


HX_RESULT CHXSiteStatusText::ParentChangedSize()
{
   UpdatePosition();
   return HXR_OK;
}


HX_RESULT CHXSiteStatusText::BringToTop()
{
   IHXSite2* pSite2 = NULL;

   if (GetIHXSite2(pSite2) == HXR_OK)
   {
      pSite2->MoveSiteToTop();
      HX_RELEASE(pSite2);
   }

   return HXR_OK;
}


HX_RESULT CHXSiteStatusText::UpdatePosition()
{
   HXxPoint	parentPos;
   HXxSize	parentSize;
   HX_RESULT    res = HXR_FAIL;

   if (m_pParentSite && m_pSite)
   {
      if (m_pParentSite->GetSize(parentSize) == HXR_OK &&
          m_pParentSite->GetPosition(parentPos) == HXR_OK)
      {
         // call platform-specific code to adjust the position & size
         // of our site (since fonts may require different heights on
         // different platforms
         _AdjustPosition(&parentPos, &parentSize);

         m_pSite->SetPosition(m_statusPos);
         m_pSite->SetSize(m_statusSize);

         if (!m_StatusText.IsEmpty())
         {
            BringToTop();
         }
         res = HXR_OK;
      }
   }

   return res;
}


void CHXSiteStatusText::SetStatusText(const char* pText)
{
   m_StatusText = pText;
   if (m_StatusText.IsEmpty())
   {
      Hide();
   }
   else
   {
      Show();
   }
}


STDMETHODIMP CHXSiteStatusText::QueryInterface(REFIID riid, void** ppvObj)
{
   if (riid == IID_IHXSiteUser)
   {
      *ppvObj = (IHXSiteUser*)this;
      AddRef();
      return HXR_OK;
   }
   if (riid == IID_IHXSiteWatcher)
   {
      *ppvObj = (IHXSiteWatcher*)this;
      AddRef();
      return HXR_OK;
   }
   else if (riid == IID_IUnknown)
   {
      *ppvObj = (IUnknown*)(IHXSiteUser*)this;
      AddRef();
      return HXR_OK;
   }

   return HXR_FAIL;
}


STDMETHODIMP_(ULONG32) CHXSiteStatusText::AddRef()
{
   return InterlockedIncrement(&m_lRefCount);
}


STDMETHODIMP_(ULONG32) CHXSiteStatusText::Release()
{
   if (InterlockedDecrement(&m_lRefCount) > 0)
   {
      return m_lRefCount;
   }

   delete this;
   return 0;
}

STDMETHODIMP CHXSiteStatusText::AttachSite(IHXSite*	/*IN*/ pSite)
{
   HX_RESULT hr = HXR_FAIL;

   if (!m_pSite)
   {
      m_pSite = pSite;
      m_pSite->AddRef();
      hr = HXR_OK;
   }

   return hr;
}


STDMETHODIMP CHXSiteStatusText::DetachSite()
{
   HX_RESULT hr = HXR_FAIL;

   if (m_pSite)
   {
      HX_RELEASE(m_pSite);
      hr = HXR_OK;
   }

   return hr;
}


STDMETHODIMP CHXSiteStatusText::HandleEvent(HXxEvent*	/*IN*/ pEvent)
{
   HX_RESULT hr = HXR_OK;

   if (pEvent->event == HX_SURFACE_UPDATE)
   {
      _DrawStatusText(pEvent);
   }

   pEvent->handled = TRUE;
   pEvent->result = hr;

   return hr;
}


STDMETHODIMP_(BOOL) CHXSiteStatusText::NeedsWindowedSites()
{
   return false;
}


HX_RESULT CHXSiteStatusText::GetIHXSite2(REF(IHXSite2*) pSite2)
{
   HX_RESULT hr = HXR_FAIL;

   if (m_pSite)
   {
      hr = m_pSite->QueryInterface(IID_IHXSite2, (void**)&pSite2);
   }

   return hr;
}


HX_RESULT CHXSiteStatusText::_DrawStatusText(HXxEvent* pEvent)
{
   HX_RESULT hr = HXR_OK;
   HX_ASSERT(pEvent);

   int         direction;
   int         ascent;
   int         descent;
   XCharStruct overall;

   if (!m_pSite || m_statusSize.cx == 0)
   {
      return HXR_UNEXPECTED;
   }


   // if this is the first time in we should populate our data now
   if (m_display == NULL)
   {
      // first get the site's window
      IHXSiteWindowed* pSiteW = NULL;
      HX_VERIFY(HXR_OK == m_pSite->QueryInterface(IID_IHXSiteWindowed, (void**)&pSiteW));

      HXxWindow* pWindow  = pSiteW->GetWindow();
      m_window = (Window) pWindow->window;
      m_display = (Display*) pWindow->display;

      XLockDisplay(m_display);
      int screenNumber = DefaultScreen(m_display);
      m_colormap = DefaultColormap(m_display, screenNumber);
      m_statusfont = XLoadQueryFont(m_display, DEFAULT_X_FONT);
      if (!m_statusfont)
      {
	  m_statusfont = XLoadQueryFont(m_display, FALLBACK_X_FONT);
      }

      XUnlockDisplay(m_display);

      HX_RELEASE(pSiteW);
   }

   // If we couldn't find a font to use then abort.
   if (!m_statusfont)
   {
       return HXR_FAIL;
   }

   // create the GC
   XLockDisplay(m_display);
   GC gc = XCreateGC(m_display, m_window, 0, 0);
   XUnlockDisplay(m_display);

   // get the GC values we will need
   unsigned long mask = GCForeground | GCBackground | GCClipXOrigin | GCClipYOrigin;
   XGCValues gcValues;
   memset(&gcValues, 0, sizeof(XGCValues));
   XLockDisplay(m_display);
   int r = XGetGCValues(m_display, gc, mask, &gcValues);
   XUnlockDisplay(m_display);

   // save off the color pixel values
   Pixel saveBG = gcValues.background;
   Pixel saveFG = gcValues.foreground;

   // convert color to Pixel values
   XColor bgXColor, fgXColor;
   ConvertRGBToXColor(m_rgbBkgndColor, bgXColor);
   ConvertRGBToXColor(m_rgbTextColor, fgXColor);

   // get color
   XLockDisplay(m_display);
   int status1 = XAllocColor(m_display, m_colormap, &bgXColor);
   XUnlockDisplay(m_display);
   if (!status1)
   {
      // default to first color (is this really enough?)
      bgXColor.pixel = 1;
   }

   XLockDisplay(m_display);
   int status2 = XAllocColor(m_display, m_colormap, &fgXColor);
   XUnlockDisplay(m_display);
   if (!status2)
   {
      // default to first color (is this really enough?)
      fgXColor.pixel = 1;
   }

   // get the size of our region
   HXxSize sz;
   m_pSite->GetSize(sz);

   // keep a local copy of status text in case we need to add an elipsis
   CHXString statusText = m_StatusText;
   int nStrLen = statusText.GetLength();
   char* pStr = statusText.GetBuffer(statusText.GetLength()+1);

   // get size of elipsis
   XTextExtents(m_statusfont, ELIPSIS, strlen(ELIPSIS), &direction, &ascent, &descent, &overall);
   int nElipsisLen = (overall.lbearing + overall.rbearing);

   XTextExtents(m_statusfont, pStr, nStrLen, &direction, &ascent, &descent, &overall);
   int nWidth = (overall.lbearing + overall.rbearing);

   while (nWidth > m_statusSize.cx - DEF_HORZ_GUTTER && nStrLen > 0)
   {
      nStrLen--;
      XTextExtents(m_statusfont, pStr, nStrLen, &direction, &ascent, &descent, &overall);
      nWidth = (overall.lbearing + overall.rbearing);
   }

   if (nStrLen < statusText.GetLength())
   {
      statusText = statusText.Left(nStrLen);
      statusText += ELIPSIS;
      nStrLen += ELIPSIS_LEN;
   }

   int nHeight = (int)((double)DEF_TEXT_HEIGHT * POINTS_TO_PIXELS);
   int nX = max(0, (m_statusSize.cx - nWidth) / 2);
   int nY = ((int)((double)m_statusSize.cy / 2 + (double)DEF_TEXT_HEIGHT/ 2)) + m_statusPos.y;

   // set our colors into the gc
   XLockDisplay(m_display);
   XSetBackground(m_display, gc, fgXColor.pixel);
   XSetForeground(m_display, gc, bgXColor.pixel);

   // fill first then draw text
   XFillRectangle(m_display, m_window, gc,
                  m_statusPos.x,
                  m_statusPos.y,
                  m_statusSize.cx,
                  m_statusSize.cy);

   XSetBackground(m_display, gc, bgXColor.pixel);
   XSetForeground(m_display, gc, fgXColor.pixel);

   // draw string
   XDrawImageString(m_display, m_window, gc, nX, nY, (const char*) statusText, statusText.GetLength());

   // reset orig. colors
   XSetBackground(m_display, gc, saveBG);
   XSetForeground(m_display, gc, saveFG);

   // clean up colors
   unsigned long pixels[2];
   pixels[0] = bgXColor.pixel;
   pixels[1] = fgXColor.pixel;
   XFreeColors(m_display, m_colormap, pixels, 2, 0);

   // release GC
   XFreeGC(m_display, gc);
   XUnlockDisplay(m_display);

   return hr;
}

BOOL CHXSiteStatusText::ConvertRGBToXColor(HXxColor& hxxColor, XColor& xcolor)
{
   // assume starting with a new XColor
   memset(&xcolor, 0, sizeof(XColor));

    // separate r,g and b
   UINT16 t;
   t = (hxxColor & 0x00ff0000) >> 16;
   xcolor.blue   = t << 8;

   t = (hxxColor & 0x0000ff00) >> 8;
   xcolor.green = t << 8;

   t = (hxxColor & 0x000000ff);
   xcolor.red  = t << 8;

   //color.pixel = n;
   xcolor.flags = DoRed | DoGreen | DoBlue;

   return TRUE;
}

HX_RESULT CHXSiteStatusText::_AdjustPosition(HXxPoint* pParentPos, HXxSize* pParentSize)
{
   HX_RESULT hr = HXR_OK;

   // this function is called internally and these will never be null
   HX_ASSERT(pParentPos && pParentSize);

   // take the max width of the parent site
   m_statusPos.x = 0;
   m_statusSize.cx = pParentSize->cx;

   // take at most the bottom DEF_STAT_HEIGHT pixels of the parent site
   m_statusPos.y = max(pParentSize->cy - DEF_STAT_HEIGHT, 0);
   m_statusSize.cy = pParentSize->cy - m_statusPos.y;

   return hr;
}
