/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * IBM Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Rich Walsh <dragtext@e-vertise.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. 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 MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#define INCL_DOSMISC
#define INCL_DOSERRORS

#include "nsDragService.h"
#include "nsXPCOM.h"
#include "nsISupportsPrimitives.h"
#include "nsString.h"
#include "nsXPIDLString.h"
#include "nsReadableUtils.h"
#include "nsIWebBrowserPersist.h"
#include "nsILocalFile.h"
#include "nsIURI.h"
#include "nsIURL.h"
#include "nsNetUtil.h"
#include "nsOS2Uni.h"
#include "wdgtos2rc.h"
#include "nsILocalFileOS2.h"
#include "nsIDocument.h"
#include "nsGUIEvent.h"
#include "nsISelection.h"

// --------------------------------------------------------------------------
// Local defines

// undocumented(?)
#ifndef DC_PREPAREITEM
  #define DC_PREPAREITEM  0x0040
#endif

// limit URL object titles to a reasonable length
#define MAXTITLELTH 31
#define TITLESEPARATOR (L' ')

#define DTSHARE_NAME    "\\SHAREMEM\\MOZ_DND"
#define DTSHARE_RMF     "<DRM_DTSHARE, DRF_TEXT>"

#define OS2FILE_NAME    "MOZ_TGT.TMP"
#define OS2FILE_TXTRMF  "<DRM_OS2FILE, DRF_TEXT>"
#define OS2FILE_UNKRMF  "<DRM_OS2FILE, DRF_UNKNOWN>"

// not defined in the OS/2 toolkit headers
extern "C" {
APIRET APIENTRY DosQueryModFromEIP(HMODULE *phMod, ULONG *pObjNum,
                                   ULONG BuffLen,  PCHAR pBuff,
                                   ULONG *pOffset, ULONG Address);
}

// --------------------------------------------------------------------------
// Helper functions

nsresult RenderToOS2File( PDRAGITEM pditem, HWND hwnd);
nsresult RenderToOS2FileComplete(PDRAGTRANSFER pdxfer, USHORT usResult,
                                 bool content, char** outText);
nsresult RenderToDTShare( PDRAGITEM pditem, HWND hwnd);
nsresult RenderToDTShareComplete(PDRAGTRANSFER pdxfer, USHORT usResult,
                                 char** outText);
nsresult RequestRendering( PDRAGITEM pditem, HWND hwnd, PCSZ pRMF, PCSZ pName);
nsresult GetAtom( ATOM aAtom, char** outText);
nsresult GetFileName(PDRAGITEM pditem, char** outText);
nsresult GetFileContents(PCSZ pszPath, char** outText);
nsresult GetTempFileName(char** outText);
void     SaveTypeAndSource(nsILocalFile *file, nsIDOMDocument *domDoc,
                           PCSZ pszType);
int      UnicodeToCodepage( const nsAString& inString, char **outText);
int      CodepageToUnicode( const nsACString& inString, PRUnichar **outText);
void     RemoveCarriageReturns(char * pszText);
MRESULT EXPENTRY nsDragWindowProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2);

// --------------------------------------------------------------------------
// Global data

static HPOINTER gPtrArray[IDC_DNDCOUNT];
static char *   gTempFile = 0;

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------

nsDragService::nsDragService()
{
    // member initializers and constructor code
  mDragWnd = WinCreateWindow( HWND_DESKTOP, WC_STATIC, 0, 0, 0, 0, 0, 0,
                              HWND_DESKTOP, HWND_BOTTOM, 0, 0, 0);
  WinSubclassWindow( mDragWnd, nsDragWindowProc);

  HMODULE hModResources = NULLHANDLE;
  DosQueryModFromEIP(&hModResources, NULL, 0, NULL, NULL, (ULONG) &gPtrArray);
  for (int i = 0; i < IDC_DNDCOUNT; i++)
    gPtrArray[i] = ::WinLoadPointer(HWND_DESKTOP, hModResources, i+IDC_DNDBASE);
}

// --------------------------------------------------------------------------

nsDragService::~nsDragService()
{
    // destructor code
  WinDestroyWindow(mDragWnd);

  for (int i = 0; i < IDC_DNDCOUNT; i++) {
    WinDestroyPointer(gPtrArray[i]);
    gPtrArray[i] = 0;
  }
}

NS_IMPL_ISUPPORTS_INHERITED1(nsDragService, nsBaseDragService, nsIDragSessionOS2)

// --------------------------------------------------------------------------

NS_IMETHODIMP nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode,
                                            nsISupportsArray *aTransferables, 
                                            nsIScriptableRegion *aRegion,
                                            PRUint32 aActionType)
{
  if (mDoingDrag)
    return NS_ERROR_UNEXPECTED;

  nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode, aTransferables,
                                                     aRegion, aActionType);
  NS_ENSURE_SUCCESS(rv, rv);

  mSourceDataItems = aTransferables;
  WinSetCapture(HWND_DESKTOP, NULLHANDLE);

    // Assume we are only dragging one thing for now
  PDRAGINFO pDragInfo = DrgAllocDraginfo(1);
  if (!pDragInfo)
    return NS_ERROR_UNEXPECTED;

  pDragInfo->usOperation = DO_DEFAULT;

  DRAGITEM dragitem;
  dragitem.hwndItem            = mDragWnd;
  dragitem.ulItemID            = (ULONG)this;
  dragitem.fsControl           = DC_OPEN;
  dragitem.cxOffset            = 2;
  dragitem.cyOffset            = 2;
  dragitem.fsSupportedOps      = DO_COPYABLE|DO_MOVEABLE|DO_LINKABLE;

    // since there is no source file, leave these "blank"
  dragitem.hstrContainerName   = NULLHANDLE;
  dragitem.hstrSourceName      = NULLHANDLE;

  rv = NS_ERROR_FAILURE;
  ULONG idIcon = 0;

    // bracket this to reduce our footprint before the drag begins
  {
    nsCOMPtr<nsISupports> genericItem;
    mSourceDataItems->GetElementAt(0, getter_AddRefs(genericItem));
    nsCOMPtr<nsITransferable> transItem (do_QueryInterface(genericItem));

    nsCOMPtr<nsISupports> genericData;
    PRUint32 len = 0;

      // see if we have a URL or text;  if so, the title method
      // will save the data and mimetype for use with a native drop

    if (NS_SUCCEEDED(transItem->GetTransferData(kURLMime,
                              getter_AddRefs(genericData), &len))) {
      nsXPIDLCString targetName;
      rv = GetUrlAndTitle( genericData, getter_Copies(targetName));
      if (NS_SUCCEEDED(rv)) {
        // advise PM that we need a DM_RENDERPREPARE msg
        // *before* it composes a render-to filename
        dragitem.fsControl     |= DC_PREPAREITEM;
        dragitem.hstrType       = DrgAddStrHandle("UniformResourceLocator");
        dragitem.hstrRMF        = DrgAddStrHandle("<DRM_OS2FILE,DRF_TEXT>");
        dragitem.hstrTargetName = DrgAddStrHandle(targetName.get());
        idIcon = IDC_DNDURL;
      }
    }
    else
    if (NS_SUCCEEDED(transItem->GetTransferData(kUnicodeMime,
                                getter_AddRefs(genericData), &len))) {
      nsXPIDLCString targetName;
      rv = GetUniTextTitle( genericData, getter_Copies(targetName));
      if (NS_SUCCEEDED(rv)) {
        dragitem.hstrType       = DrgAddStrHandle("Plain Text");
        dragitem.hstrRMF        = DrgAddStrHandle("<DRM_OS2FILE,DRF_TEXT>");
        dragitem.hstrTargetName = DrgAddStrHandle(targetName.get());
        idIcon = IDC_DNDTEXT;
      }
    }
  }

    // if neither URL nor text are available, make this a Moz-only drag
    // by making it unidentifiable to native apps
  if (NS_FAILED(rv)) {
    mMimeType = 0;
    dragitem.hstrType       = DrgAddStrHandle("Unknown");
    dragitem.hstrRMF        = DrgAddStrHandle("<DRM_UNKNOWN,DRF_UNKNOWN>");
    dragitem.hstrTargetName = NULLHANDLE;
  }
  DrgSetDragitem(pDragInfo, &dragitem, sizeof(DRAGITEM), 0);

  DRAGIMAGE dragimage;
  memset(&dragimage, 0, sizeof(DRAGIMAGE));
  dragimage.cb = sizeof(DRAGIMAGE);
  dragimage.fl = DRG_ICON;
  if (idIcon)
    dragimage.hImage = gPtrArray[idIcon-IDC_DNDBASE];
  if (dragimage.hImage) {
    dragimage.cyOffset = 8;
    dragimage.cxOffset = 2;
  }
  else
    dragimage.hImage  = WinQuerySysPointer(HWND_DESKTOP, SPTR_FILE, FALSE);
    
  mDoingDrag = true;
  LONG escState = WinGetKeyState(HWND_DESKTOP, VK_ESC) & 0x01;
  HWND hwndDest = DrgDrag(mDragWnd, pDragInfo, &dragimage, 1, VK_BUTTON2,
                  (void*)0x80000000L); // Don't lock the desktop PS

    // determine whether the drag ended because Escape was pressed
  if (hwndDest == 0 && (WinGetKeyState(HWND_DESKTOP, VK_ESC) & 0x01) != escState)
    mUserCancelled = true;
  FireDragEventAtSource(NS_DRAGDROP_END);
  mDoingDrag = false;

    // do clean up;  if the drop completed,
    // the target will delete the string handles
  if (hwndDest == 0)
      DrgDeleteDraginfoStrHandles(pDragInfo);
  DrgFreeDraginfo(pDragInfo);

    // reset nsDragService's members
  mSourceDataItems = 0;
  mSourceData = 0;
  mMimeType = 0;

    // reset nsBaseDragService's members
  mSourceDocument = nsnull;
  mSourceNode = nsnull;
  mSelection = nsnull;
  mDataTransfer = nsnull;
  mUserCancelled = false;
  mHasImage = false;
  mImage = nsnull;
  mImageX = 0;
  mImageY = 0;
  mScreenX = -1;
  mScreenY = -1;

  return NS_OK;
}

// --------------------------------------------------------------------------

MRESULT EXPENTRY nsDragWindowProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  switch (msg) {

      // if the user requests the contents of a URL be rendered (vs the URL
      // itself), change the suggested target name from the URL's title to
      // the name of the file that will be retrieved
    case DM_RENDERPREPARE: {
      PDRAGTRANSFER  pdxfer = (PDRAGTRANSFER)mp1;
      nsDragService* dragservice = (nsDragService*)pdxfer->pditem->ulItemID;
  
      if (pdxfer->usOperation == DO_COPY &&
          (WinGetKeyState(HWND_DESKTOP, VK_CTRL) & 0x8000) &&
          !strcmp(dragservice->mMimeType, kURLMime)) {
          // QI'ing nsIURL will fail for mailto: and the like
        nsCOMPtr<nsIURL> urlObject(do_QueryInterface(dragservice->mSourceData));
        if (urlObject) {
          nsCAutoString filename;
          urlObject->GetFileName(filename);
          if (filename.IsEmpty()) {
            urlObject->GetHost(filename);
            filename.Append("/file");
          }
          DrgDeleteStrHandle(pdxfer->pditem->hstrTargetName);
          pdxfer->pditem->hstrTargetName = DrgAddStrHandle(filename.get());
        }
      }
      return (MRESULT)TRUE;
    }
  
    case DM_RENDER: {
      nsresult       rv = NS_ERROR_FAILURE;
      PDRAGTRANSFER  pdxfer = (PDRAGTRANSFER)mp1;
      nsDragService* dragservice = (nsDragService*)pdxfer->pditem->ulItemID;
      char           chPath[CCHMAXPATH];

      DrgQueryStrName(pdxfer->hstrRenderToName, CCHMAXPATH, chPath);

        // if the user Ctrl-dropped a URL, use the nsIURL interface
        // to determine if it points to content (i.e. a file);  if so,
        // fetch its contents; if not (e.g. a 'mailto:' url), drop into
        // the code that uses nsIURI to render a URL object

      if (!strcmp(dragservice->mMimeType, kURLMime)) {
        if (pdxfer->usOperation == DO_COPY &&
            (WinGetKeyState(HWND_DESKTOP, VK_CTRL) & 0x8000)) {
          nsCOMPtr<nsIURL> urlObject(do_QueryInterface(dragservice->mSourceData));
          if (urlObject)
            rv = dragservice->SaveAsContents(chPath, urlObject);
        }
        if (!NS_SUCCEEDED(rv)) {
          nsCOMPtr<nsIURI> uriObject(do_QueryInterface(dragservice->mSourceData));
          if (uriObject)
            rv = dragservice->SaveAsURL(chPath, uriObject);
        }
      }
      else
          // if we're dragging text, do NLS conversion then write it to file
        if (!strcmp(dragservice->mMimeType, kUnicodeMime)) {
          nsCOMPtr<nsISupportsString> strObject(
                                 do_QueryInterface(dragservice->mSourceData));
          if (strObject)
            rv = dragservice->SaveAsText(chPath, strObject);
        }
  
      DrgPostTransferMsg(pdxfer->hwndClient, DM_RENDERCOMPLETE, pdxfer,
                         (NS_SUCCEEDED(rv) ? DMFL_RENDEROK : DMFL_RENDERFAIL),
                         0, TRUE);
      DrgFreeDragtransfer(pdxfer);
      return (MRESULT)TRUE;
    }

      // we don't use these msgs but neither does WinDefWindowProc()
    case DM_DRAGOVERNOTIFY:
    case DM_ENDCONVERSATION:
      return 0;
  
    default:
      break;
  }

  return ::WinDefWindowProc(hWnd, msg, mp1, mp2);
}

//-------------------------------------------------------------------------

// if the versions of Start & EndDragSession in nsBaseDragService
// were called (and they shouldn't be), they'd break nsIDragSessionOS2;
// they're overridden here and turned into no-ops to prevent this

NS_IMETHODIMP nsDragService::StartDragSession()
{
  NS_ERROR("OS/2 version of StartDragSession() should never be called!");
  return NS_OK;
}

NS_IMETHODIMP nsDragService::EndDragSession(bool aDragDone)
{
  NS_ERROR("OS/2 version of EndDragSession() should never be called!");
  return NS_OK;
}

// --------------------------------------------------------------------------

NS_IMETHODIMP nsDragService::GetNumDropItems(PRUint32 *aNumDropItems)
{
  if (mSourceDataItems)
    mSourceDataItems->Count(aNumDropItems);
  else
    *aNumDropItems = 0;

  return NS_OK;
}

// --------------------------------------------------------------------------

NS_IMETHODIMP nsDragService::GetData(nsITransferable *aTransferable,
                                     PRUint32 aItemIndex)
{
    // make sure that we have a transferable
  if (!aTransferable)
    return NS_ERROR_INVALID_ARG;

    // get flavor list that includes all acceptable flavors (including
    // ones obtained through conversion). Flavors are nsISupportsCStrings
    // so that they can be seen from JS.
  nsresult rv = NS_ERROR_FAILURE;
  nsCOMPtr<nsISupportsArray> flavorList;
  rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
  if (NS_FAILED(rv))
    return rv;

    // count the number of flavors
  PRUint32 cnt;
  flavorList->Count (&cnt);

  for (unsigned int i= 0; i < cnt; ++i ) {
    nsCOMPtr<nsISupports> genericWrapper;
    flavorList->GetElementAt(i, getter_AddRefs(genericWrapper));
    nsCOMPtr<nsISupportsCString> currentFlavor;
    currentFlavor = do_QueryInterface(genericWrapper);
    if (currentFlavor) {
      nsXPIDLCString flavorStr;
      currentFlavor->ToString(getter_Copies(flavorStr));
  
      nsCOMPtr<nsISupports> genericItem;
  
      mSourceDataItems->GetElementAt(aItemIndex, getter_AddRefs(genericItem));
      nsCOMPtr<nsITransferable> item (do_QueryInterface(genericItem));
      if (item) {
        nsCOMPtr<nsISupports> data;
        PRUint32 tmpDataLen = 0;
        rv = item->GetTransferData(flavorStr, getter_AddRefs(data),
                                   &tmpDataLen);
        if (NS_SUCCEEDED(rv)) {
          rv = aTransferable->SetTransferData(flavorStr, data, tmpDataLen);
          break;
        }
      }
    }
  }

  return rv;
}

// --------------------------------------------------------------------------

// This returns true if any of the dragged items support a specified data
// flavor.  This doesn't make a lot of sense when dragging multiple items:
// all of them ought to match.  OTOH, Moz doesn't support multiple drag
// items so no problems arise.  If they do, use the commented-out code to
// switch from "any" to "all".

NS_IMETHODIMP nsDragService::IsDataFlavorSupported(const char *aDataFlavor,
                                                   bool *_retval)
{
  if (!_retval)
    return NS_ERROR_INVALID_ARG;

  *_retval = false;

  PRUint32 numDragItems = 0;
  if (mSourceDataItems)
    mSourceDataItems->Count(&numDragItems);
  if (!numDragItems)
    return NS_OK;

// return true if all items support this flavor
//  for (PRUint32 itemIndex = 0, *_retval = true;
//       itemIndex < numDragItems && *_retval; ++itemIndex) {
//    *_retval = false;

// return true if any item supports this flavor
  for (PRUint32 itemIndex = 0;
       itemIndex < numDragItems && !(*_retval); ++itemIndex) {

    nsCOMPtr<nsISupports> genericItem;
    mSourceDataItems->GetElementAt(itemIndex, getter_AddRefs(genericItem));
    nsCOMPtr<nsITransferable> currItem (do_QueryInterface(genericItem));

    if (currItem) {
      nsCOMPtr <nsISupportsArray> flavorList;
      currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList));

      if (flavorList) {
        PRUint32 numFlavors;
        flavorList->Count( &numFlavors );

        for (PRUint32 flavorIndex=0; flavorIndex < numFlavors; ++flavorIndex) {
          nsCOMPtr<nsISupports> genericWrapper;
          flavorList->GetElementAt(flavorIndex, getter_AddRefs(genericWrapper));
          nsCOMPtr<nsISupportsCString> currentFlavor;
          currentFlavor = do_QueryInterface(genericWrapper);

          if (currentFlavor) {
            nsXPIDLCString flavorStr;
            currentFlavor->ToString ( getter_Copies(flavorStr) );
            if (strcmp(flavorStr, aDataFlavor) == 0) {
              *_retval = true;
              break;
            }
          }
        } // for each flavor
      }
    }
  }

  return NS_OK;
}

// --------------------------------------------------------------------------

// use nsIWebBrowserPersist to fetch the contents of a URL

nsresult nsDragService::SaveAsContents(PCSZ pszDest, nsIURL* aURL)
{
  nsCOMPtr<nsIURI> linkURI(do_QueryInterface(aURL));
  if (!linkURI)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIWebBrowserPersist> webPersist(
    do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1"));
  if (!webPersist)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsILocalFile> file;
  NS_NewNativeLocalFile(nsDependentCString(pszDest), true,
                        getter_AddRefs(file));
  if (!file)
    return NS_ERROR_FAILURE;

  FILE* fp;
  if (NS_FAILED(file->OpenANSIFileDesc("wb+", &fp)))
    return NS_ERROR_FAILURE;

  fwrite("", 0, 1, fp);
  fclose(fp);
  webPersist->SaveURI(linkURI, nsnull, nsnull, nsnull, nsnull, file);

  return NS_OK;
}

// --------------------------------------------------------------------------

// save this URL in a file that the WPS will identify as a WPUrl object

nsresult nsDragService::SaveAsURL(PCSZ pszDest, nsIURI* aURI)
{
  nsCAutoString strUri;
  aURI->GetSpec(strUri);

  if (strUri.IsEmpty())
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsILocalFile> file;
  NS_NewNativeLocalFile(nsDependentCString(pszDest), true,
                        getter_AddRefs(file));
  if (!file)
    return NS_ERROR_FAILURE;

  FILE* fp;
  if (NS_FAILED(file->OpenANSIFileDesc("wb+", &fp)))
    return NS_ERROR_FAILURE;

  fwrite(strUri.get(), strUri.Length(), 1, fp);
  fclose(fp);

  nsCOMPtr<nsIDOMDocument> domDoc;
  GetSourceDocument(getter_AddRefs(domDoc));
  SaveTypeAndSource(file, domDoc, "UniformResourceLocator");

  return NS_OK;
}

// --------------------------------------------------------------------------

// save this text to file after conversion to the current codepage

nsresult nsDragService::SaveAsText(PCSZ pszDest, nsISupportsString* aString)
{
  nsAutoString strData;
  aString->GetData(strData);

  if (strData.IsEmpty())
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsILocalFile> file;
  NS_NewNativeLocalFile(nsDependentCString(pszDest), true,
                        getter_AddRefs(file));
  if (!file)
    return NS_ERROR_FAILURE;

  nsXPIDLCString textStr;
  int cnt = UnicodeToCodepage(strData, getter_Copies(textStr));
  if (!cnt)
    return NS_ERROR_FAILURE;

  FILE* fp;
  if (NS_FAILED(file->OpenANSIFileDesc("wb+", &fp)))
    return NS_ERROR_FAILURE;

  fwrite(textStr.get(), cnt, 1, fp);
  fclose(fp);

  nsCOMPtr<nsIDOMDocument> domDoc;
  GetSourceDocument(getter_AddRefs(domDoc));
  SaveTypeAndSource(file, domDoc, "Plain Text");

  return NS_OK;
}

// --------------------------------------------------------------------------

// Split a Moz Url/Title into its components, save the Url for use by
// a native drop, then compose a title.

nsresult  nsDragService::GetUrlAndTitle(nsISupports *aGenericData,
                                        char **aTargetName)
{
    // get the URL/title string
  nsCOMPtr<nsISupportsString> strObject ( do_QueryInterface(aGenericData));
  if (!strObject)
    return NS_ERROR_FAILURE;
  nsAutoString strData;
  strObject->GetData(strData);

    // split string into URL and Title - 
    // if there's a title but no URL, there's no reason to continue
  PRInt32 lineIndex = strData.FindChar ('\n');
  if (lineIndex == 0)
    return NS_ERROR_FAILURE;

    // get the URL portion of the text
  nsAutoString strUrl;
  if (lineIndex == -1)
    strUrl = strData;
  else
    strData.Left(strUrl, lineIndex);

    // save the URL for later use
  nsCOMPtr<nsIURI> saveURI;
  NS_NewURI(getter_AddRefs(saveURI), strUrl);
  if (!saveURI)
    return NS_ERROR_FAILURE;

    // if there's a bona-fide title & it isn't just a copy of the URL,
    // limit it to a reasonable length, perform NLS conversion, then return

  if (++lineIndex && lineIndex != (int)strData.Length() &&
      !strUrl.Equals(Substring(strData, lineIndex, strData.Length()))) {
    PRUint32 strLth = NS_MIN((int)strData.Length()-lineIndex, MAXTITLELTH);
    nsAutoString strTitle;
    strData.Mid(strTitle, lineIndex, strLth);
    if (!UnicodeToCodepage(strTitle, aTargetName))
      return NS_ERROR_FAILURE;

    mSourceData = do_QueryInterface(saveURI);
    mMimeType = kURLMime;
    return NS_OK;
  }

    // if the URI can be handled as a URL, construct a title from
    // the hostname & filename;  if not, use the first MAXTITLELTH
    // characters that appear after the scheme name

  nsCAutoString strTitle;

  nsCOMPtr<nsIURL> urlObj( do_QueryInterface(saveURI));
  if (urlObj) {
    nsCAutoString strFile;

    urlObj->GetHost(strTitle);
    urlObj->GetFileName(strFile);
    if (!strFile.IsEmpty()) {
      strTitle.AppendLiteral("/");
      strTitle.Append(strFile);
    }
    else {
      urlObj->GetDirectory(strFile);
      if (strFile.Length() > 1) {
        nsCAutoString::const_iterator start, end, curr;
        strFile.BeginReading(start);
        strFile.EndReading(end);
        strFile.EndReading(curr);
        for (curr.advance(-2); curr != start; --curr)
          if (*curr == '/')
            break;
        strTitle.Append(Substring(curr, end));
      }
    }
  }
  else {
    saveURI->GetSpec(strTitle);
    PRInt32 index = strTitle.FindChar (':');
    if (index != -1) {
      if ((strTitle.get())[++index] == '/')
        if ((strTitle.get())[++index] == '/')
          ++index;
      strTitle.Cut(0, index);
    }
    if (strTitle.Length() > MAXTITLELTH)
      strTitle.Truncate(MAXTITLELTH);
  }

  *aTargetName = ToNewCString(strTitle);

  mSourceData = do_QueryInterface(saveURI);
  mMimeType = kURLMime;
  return NS_OK;
}

// --------------------------------------------------------------------------

// Construct a title for text drops from the leading words of the text.
// Alphanumeric characters are copied to the title;  sequences of
// non-alphanums are replaced by a single space

nsresult  nsDragService::GetUniTextTitle(nsISupports *aGenericData,
                                         char **aTargetName)
{
    // get the string
  nsCOMPtr<nsISupportsString> strObject ( do_QueryInterface(aGenericData));
  if (!strObject)
    return NS_ERROR_FAILURE;

    // alloc a buffer to hold the unicode title text
  int bufsize = (MAXTITLELTH+1)*2;
  PRUnichar * buffer = (PRUnichar*)nsMemory::Alloc(bufsize);
  if (!buffer)
    return NS_ERROR_FAILURE;

  nsAutoString strData;
  strObject->GetData(strData);
  nsAutoString::const_iterator start, end;
  strData.BeginReading(start);
  strData.EndReading(end);

    // skip over leading non-alphanumerics
  for( ; start != end; ++start)
    if (UniQueryChar( *start, CT_ALNUM))
      break;

    // move alphanumerics into the buffer & replace contiguous
    // non-alnums with a single separator character
  int ctr, sep;
  for (ctr=0, sep=0; start != end && ctr < MAXTITLELTH; ++start) {
    if (UniQueryChar( *start, CT_ALNUM)) {
      buffer[ctr] = *start;
      ctr++;
      sep = 0;
    }
    else
      if (!sep) {
        buffer[ctr] = TITLESEPARATOR;
        ctr++;
        sep = 1;
      }
  }
    // eliminate trailing separators & lone characters
    // orphaned when the title is truncated
  if (sep)
    ctr--;
  if (ctr >= MAXTITLELTH - sep && buffer[ctr-2] == TITLESEPARATOR)
    ctr -= 2;
  buffer[ctr] = 0;

    // if we ended up with no alnums, call the result "text";
    // otherwise, do NLS conversion
  if (!ctr) {
    *aTargetName = ToNewCString(NS_LITERAL_CSTRING("text"));
    ctr = 1;
  }
  else
    ctr = UnicodeToCodepage( nsDependentString(buffer), aTargetName);

    // free our buffer, then exit
  nsMemory::Free(buffer);

  if (!ctr)
  return NS_ERROR_FAILURE;

  mSourceData = aGenericData;
  mMimeType = kUnicodeMime;
  return NS_OK;
}

// --------------------------------------------------------------------------
// nsIDragSessionOS2
// --------------------------------------------------------------------------

// DragOverMsg() provides minimal handling if a drag session is already
// in progress.  If not, it assumes this is a native drag that has just
// entered the window and calls NativeDragEnter() to start a session.

NS_IMETHODIMP nsDragService::DragOverMsg(PDRAGINFO pdinfo, MRESULT &mr,
                                         PRUint32* dragFlags)
{
  nsresult  rv = NS_ERROR_FAILURE;

  if (!&mr || !dragFlags || !pdinfo || !DrgAccessDraginfo(pdinfo))
    return rv;

  *dragFlags = 0;
  mr = MRFROM2SHORT(DOR_NEVERDROP, 0);

    // examine the dragged item & "start" a drag session if OK;
    // also, signal the need for a dragenter event
  if (!mDoingDrag)
    if (NS_SUCCEEDED(NativeDragEnter(pdinfo)))
      *dragFlags |= DND_DISPATCHENTEREVENT;

    // if we're in a drag, set it up to be dispatched
  if (mDoingDrag) {
    SetCanDrop(false);
    switch (pdinfo->usOperation) {
      case DO_COPY:
        SetDragAction(DRAGDROP_ACTION_COPY);
        break;
      case DO_LINK:
        SetDragAction(DRAGDROP_ACTION_LINK);
        break;
      default:
        SetDragAction(DRAGDROP_ACTION_MOVE);
        break;
    }
    if (mSourceNode)
      *dragFlags |= DND_DISPATCHEVENT | DND_GETDRAGOVERRESULT | DND_MOZDRAG;
    else
      *dragFlags |= DND_DISPATCHEVENT | DND_GETDRAGOVERRESULT | DND_NATIVEDRAG;
    rv = NS_OK;
  }

  DrgFreeDraginfo(pdinfo);
  return rv;
}

// --------------------------------------------------------------------------

// Evaluates native drag data, and if acceptable, creates & stores
// a transferable with the available flavors (but not the data);
// if successful, it "starts" the session.

NS_IMETHODIMP nsDragService::NativeDragEnter(PDRAGINFO pdinfo)
{
  nsresult  rv = NS_ERROR_FAILURE;
  bool      isFQFile = FALSE;
  bool      isAtom = FALSE;
  PDRAGITEM pditem = 0;

  if (pdinfo->cditem != 1)
    return rv;

  pditem = DrgQueryDragitemPtr(pdinfo, 0);

  if (pditem) {
    if (DrgVerifyRMF(pditem, "DRM_ATOM", 0)) {
      isAtom = TRUE;
      rv = NS_OK;
    }
    else
    if (DrgVerifyRMF(pditem, "DRM_DTSHARE", 0))
      rv = NS_OK;
    else
    if (DrgVerifyRMF(pditem, "DRM_OS2FILE", 0)) {
      rv = NS_OK;
      if (pditem->hstrContainerName && pditem->hstrSourceName)
        isFQFile = TRUE;
    }
  }

  if (NS_SUCCEEDED(rv)) {
    rv = NS_ERROR_FAILURE;
    nsCOMPtr<nsITransferable> trans(
            do_CreateInstance("@mozilla.org/widget/transferable;1", &rv));
    if (trans) {

      bool isUrl = DrgVerifyType(pditem, "UniformResourceLocator");
      bool isAlt = (WinGetKeyState(HWND_DESKTOP, VK_ALT) & 0x8000);

        // if this is a fully-qualified file or the item claims to be
        // a Url, identify it as a Url & also offer it as html
      if ((isFQFile && !isAlt) || isUrl) {
        trans->AddDataFlavor(kURLMime);
        trans->AddDataFlavor(kHTMLMime);
      }

        // everything is always "text"
      trans->AddDataFlavor(kUnicodeMime);

        // if we can create the array, initialize the session
      nsCOMPtr<nsISupportsArray> transArray(
                    do_CreateInstance("@mozilla.org/supports-array;1", &rv));
      if (transArray) {
        transArray->InsertElementAt(trans, 0);
        mSourceDataItems = transArray;

        // add the dragged data to the transferable if we have easy access
        // to it (i.e. no need to read a file or request rendering);  for
        // URLs, we'll skip creating a title until the drop occurs
        nsXPIDLCString someText;
        if (isAtom) {
          if (NS_SUCCEEDED(GetAtom(pditem->ulItemID, getter_Copies(someText))))
            NativeDataToTransferable( someText.get(), 0, isUrl);
        }
        else
        if (isFQFile && !isAlt &&
            NS_SUCCEEDED(GetFileName(pditem, getter_Copies(someText)))) {
          nsCOMPtr<nsILocalFile> file;
          if (NS_SUCCEEDED(NS_NewNativeLocalFile(someText, true,
                                                 getter_AddRefs(file)))) {
            nsCAutoString textStr;
            NS_GetURLSpecFromFile(file, textStr);
            if (!textStr.IsEmpty()) {
              someText.Assign(ToNewCString(textStr));
              NativeDataToTransferable( someText.get(), 0, TRUE);
            }
          }
        }

        mSourceNode = 0;
        mSourceDocument = 0;
        mDoingDrag = TRUE;
        rv = NS_OK;
      }
    }
  }

  return rv;
}

// --------------------------------------------------------------------------

// Invoked after a dragover event has been dispatched, this constructs
// a reply to DM_DRAGOVER based on the canDrop & dragAction attributes.

NS_IMETHODIMP nsDragService::GetDragoverResult(MRESULT& mr)
{
  nsresult  rv = NS_ERROR_FAILURE;
  if (!&mr)
    return rv;

  if (mDoingDrag) {

    bool canDrop = false;
    USHORT usDrop;
    GetCanDrop(&canDrop);
    if (canDrop)
      usDrop = DOR_DROP;
    else
      usDrop = DOR_NODROP;

    PRUint32 action;
    USHORT   usOp;
    GetDragAction(&action);
    if (action & DRAGDROP_ACTION_COPY)
      usOp = DO_COPY;
    else
    if (action & DRAGDROP_ACTION_LINK)
      usOp = DO_LINK;
    else {
      if (mSourceNode)
        usOp = DO_MOVE;
      else
        usOp = DO_UNKNOWN+1;
      if (action == DRAGDROP_ACTION_NONE)
        usDrop = DOR_NODROP;
    }

    mr = MRFROM2SHORT(usDrop, usOp);
    rv = NS_OK;
  }
  else
    mr = MRFROM2SHORT(DOR_NEVERDROP, 0);

  return rv;
}

// --------------------------------------------------------------------------

// have the client dispatch the event, then call ExitSession()

NS_IMETHODIMP nsDragService::DragLeaveMsg(PDRAGINFO pdinfo, PRUint32* dragFlags)
{
  if (!mDoingDrag || !dragFlags)
    return NS_ERROR_FAILURE;

  if (mSourceNode)
    *dragFlags = DND_DISPATCHEVENT | DND_EXITSESSION | DND_MOZDRAG;
  else
    *dragFlags = DND_DISPATCHEVENT | DND_EXITSESSION | DND_NATIVEDRAG;

  return NS_OK;
}

// --------------------------------------------------------------------------

// DropHelp occurs when you press F1 during a drag;  apparently,
// it's like a regular drop in that the target has to do clean up

NS_IMETHODIMP nsDragService::DropHelpMsg(PDRAGINFO pdinfo, PRUint32* dragFlags)
{
  if (!mDoingDrag)
    return NS_ERROR_FAILURE;

  if (pdinfo && DrgAccessDraginfo(pdinfo)) {
    DrgDeleteDraginfoStrHandles(pdinfo);
    DrgFreeDraginfo(pdinfo);
  }

  if (!dragFlags)
    return NS_ERROR_FAILURE;

  if (mSourceNode)
    *dragFlags = DND_DISPATCHEVENT | DND_EXITSESSION | DND_MOZDRAG;
  else
    *dragFlags = DND_DISPATCHEVENT | DND_EXITSESSION | DND_NATIVEDRAG;

  return NS_OK;
}

// --------------------------------------------------------------------------

// for native drags, clean up;
// for all drags, signal that Moz is no longer in d&d mode

NS_IMETHODIMP nsDragService::ExitSession(PRUint32* dragFlags)
{
  if (!mDoingDrag)
    return NS_ERROR_FAILURE;

  if (!mSourceNode) {
    mSourceDataItems = 0;
    mDataTransfer = 0;
    mDoingDrag = FALSE;

      // if we created a temp file, delete it
    if (gTempFile) {
      DosDelete(gTempFile);
      nsMemory::Free(gTempFile);
      gTempFile = 0;
    }
  }

  if (!dragFlags)
    return NS_ERROR_FAILURE;
  *dragFlags = 0;

  return NS_OK;
}

// --------------------------------------------------------------------------

// If DropMsg() is presented with native data that has to be rendered,
// the drop event & cleanup will be defered until the client's window
// has received a render-complete msg.

NS_IMETHODIMP nsDragService::DropMsg(PDRAGINFO pdinfo, HWND hwnd,
                                     PRUint32* dragFlags)
{
  if (!mDoingDrag || !dragFlags || !pdinfo || !DrgAccessDraginfo(pdinfo))
    return NS_ERROR_FAILURE;

  switch (pdinfo->usOperation) {
    case DO_MOVE:
      SetDragAction(DRAGDROP_ACTION_MOVE);
      break;
    case DO_COPY:
      SetDragAction(DRAGDROP_ACTION_COPY);
      break;
    case DO_LINK:
      SetDragAction(DRAGDROP_ACTION_LINK);
      break;
    default:  // avoid "moving" (i.e. deleting) native text/objects
      if (mSourceNode)
        SetDragAction(DRAGDROP_ACTION_MOVE);
      else
        SetDragAction(DRAGDROP_ACTION_COPY);
      break;
  }

    // if this is a native drag, move the source data to a transferable;
    // request rendering if needed
  nsresult rv = NS_OK;
  bool rendering = false;
  if (!mSourceNode)
    rv = NativeDrop( pdinfo, hwnd, &rendering);

    // note: NativeDrop() sends an end-conversation msg to native
    // sources but nothing sends them to Mozilla - however, Mozilla
    // (i.e. nsDragService) doesn't need them, so...

    // if rendering, the action flags are off because we don't want
    // the client to do anything yet;  the status flags are off because
    // we'll be exiting d&d mode before the next screen update occurs
  if (rendering)
    *dragFlags = 0;
  else {
    // otherwise, set the flags & free the native drag structures

    *dragFlags = DND_EXITSESSION;
    if (NS_SUCCEEDED(rv)) {
      if (mSourceNode)
        *dragFlags |= DND_DISPATCHEVENT | DND_INDROP | DND_MOZDRAG;
      else
        *dragFlags |= DND_DISPATCHEVENT | DND_INDROP | DND_NATIVEDRAG;
    }

    DrgDeleteDraginfoStrHandles(pdinfo);
    DrgFreeDraginfo(pdinfo);
    rv = NS_OK;
  }

  return rv;
}

// --------------------------------------------------------------------------

// Invoked by DropMsg to fill a transferable with native data;
// if necessary, requests the source to render it.

NS_IMETHODIMP nsDragService::NativeDrop(PDRAGINFO pdinfo, HWND hwnd,
                                        bool* rendering)
{
  *rendering = false;

  nsresult rv = NS_ERROR_FAILURE;
  PDRAGITEM pditem = DrgQueryDragitemPtr(pdinfo, 0);
  if (!pditem)
    return rv;

  nsXPIDLCString dropText;
  bool isUrl = DrgVerifyType(pditem, "UniformResourceLocator");

    // identify format; the order of evaluation here is important

    // DRM_ATOM - DragText stores up to 255 chars in a Drg API atom
    // DRM_DTSHARE - DragText renders up to 1mb to named shared mem
  if (DrgVerifyRMF(pditem, "DRM_ATOM", 0))
    rv = GetAtom(pditem->ulItemID, getter_Copies(dropText));
  else
  if (DrgVerifyRMF(pditem, "DRM_DTSHARE", 0)) {
    rv = RenderToDTShare( pditem, hwnd);
    if (NS_SUCCEEDED(rv))
      *rendering = true;
  }

    // DRM_OS2FILE - get the file's path or contents if it exists;
    // request rendering if it doesn't
  else
  if (DrgVerifyRMF(pditem, "DRM_OS2FILE", 0)) {
    bool isAlt = (WinGetKeyState(HWND_DESKTOP, VK_ALT) & 0x8000);

      // the file has to be rendered - currently, we only present
      // its content, not its name, to Moz to avoid conflicts
    if (!pditem->hstrContainerName || !pditem->hstrSourceName) {
      rv = RenderToOS2File( pditem, hwnd);
      if (NS_SUCCEEDED(rv))
        *rendering = true;
    }
      // for Url objects and 'Alt+Drop', get the file's contents;
      // otherwise, convert it's path to a Url
    else {
      nsXPIDLCString fileName;
      if (NS_SUCCEEDED(GetFileName(pditem, getter_Copies(fileName)))) {
        if (isUrl || isAlt)
          rv = GetFileContents(fileName.get(), getter_Copies(dropText));
        else {
          isUrl = true;
          nsCOMPtr<nsILocalFile> file;
          if (NS_SUCCEEDED(NS_NewNativeLocalFile(fileName,
                                         true, getter_AddRefs(file)))) {
            nsCAutoString textStr;
            NS_GetURLSpecFromFile(file, textStr);
            if (!textStr.IsEmpty()) {
              dropText.Assign(ToNewCString(textStr));
              rv = NS_OK;
            }
          }
        } // filename as Url
      } // got filename
    } // existing files
  } // DRM_OS2FILE

    // if OK, put what data there is in the transferable;  this could be
    // everything needed or just the title of a Url that needs rendering
  if (NS_SUCCEEDED(rv)) {

      // for Urls, get the title & remove any linefeeds
    nsXPIDLCString titleText;
    if (isUrl &&
        pditem->hstrTargetName &&
        NS_SUCCEEDED(GetAtom(pditem->hstrTargetName, getter_Copies(titleText))))
      for (char* ptr=strchr(titleText.BeginWriting(),'\n'); ptr; ptr=strchr(ptr, '\n'))
        *ptr = ' ';

    rv = NativeDataToTransferable( dropText.get(), titleText.get(), isUrl);
  }

    // except for renderings, tell the source we're done with the data
  if (!*rendering)
    DrgSendTransferMsg( pditem->hwndItem, DM_ENDCONVERSATION,
                        (MPARAM)pditem->ulItemID,
                        (MPARAM)DMFL_TARGETSUCCESSFUL);

  return (rv);
}

// --------------------------------------------------------------------------

// Because RenderCompleteMsg() is called after the native (PM) drag
// session has ended, all of the drag status flags should be off.
//
// FYI... PM's asynchronous rendering mechanism is not compatible with
// nsIDataFlavorProvider which expects data to be rendered synchronously

NS_IMETHODIMP nsDragService::RenderCompleteMsg(PDRAGTRANSFER pdxfer,
                                        USHORT usResult, PRUint32* dragFlags)
{
  nsresult rv = NS_ERROR_FAILURE;
  if (!mDoingDrag || !pdxfer)
    return rv;

    // this msg should never come from Moz - if it does, fail
  if (!mSourceNode)
    rv = NativeRenderComplete(pdxfer, usResult);

    // DrgQueryDraginfoPtrFromDragitem() doesn't work - this does
  PDRAGINFO pdinfo = (PDRAGINFO)MAKEULONG(0x2c, HIUSHORT(pdxfer->pditem));

  DrgDeleteStrHandle(pdxfer->hstrSelectedRMF);
  DrgDeleteStrHandle(pdxfer->hstrRenderToName);
  DrgFreeDragtransfer(pdxfer);

    // if the source is Moz, don't touch pdinfo - it's been freed already
  if (pdinfo && !mSourceNode) {
    DrgDeleteDraginfoStrHandles(pdinfo);
    DrgFreeDraginfo(pdinfo);
  }

    // this shouldn't happen
  if (!dragFlags)
    return (ExitSession(dragFlags));

    // d&d is over, so the DND_DragStatus flags should all be off
  *dragFlags = DND_EXITSESSION;
  if (NS_SUCCEEDED(rv))
    *dragFlags |= DND_DISPATCHEVENT;

    // lie so the client will honor the exit-session flag
  return NS_OK;
}

// --------------------------------------------------------------------------

// this is here to provide a false sense of symmetry with the other
// method-pairs - rendered data always comes from a native source

NS_IMETHODIMP nsDragService::NativeRenderComplete(PDRAGTRANSFER pdxfer,
                                                  USHORT usResult)
{
  nsresult rv = NS_ERROR_FAILURE;
  nsXPIDLCString rmf;

    // identify the rendering mechanism, then get the data
  if (NS_SUCCEEDED(GetAtom(pdxfer->hstrSelectedRMF, getter_Copies(rmf)))) {
    nsXPIDLCString dropText;
    if (!strcmp(rmf.get(), DTSHARE_RMF))
      rv = RenderToDTShareComplete(pdxfer, usResult, getter_Copies(dropText));
    else
    if (!strcmp(rmf.get(), OS2FILE_TXTRMF) ||
        !strcmp(rmf.get(), OS2FILE_UNKRMF))
      rv = RenderToOS2FileComplete(pdxfer, usResult, true,
                                   getter_Copies(dropText));

    if (NS_SUCCEEDED(rv)) {
      bool isUrl = false;
      IsDataFlavorSupported(kURLMime, &isUrl);
      rv = NativeDataToTransferable( dropText.get(), 0, isUrl);
    }
  }

  DrgSendTransferMsg(pdxfer->hwndClient, DM_ENDCONVERSATION,
                     (MPARAM)pdxfer->ulTargetInfo,
                     (MPARAM)DMFL_TARGETSUCCESSFUL);

  return rv;
}

// --------------------------------------------------------------------------

// fills the transferable created by NativeDragEnter with
// the set of flavors and data the target will see onDrop

NS_IMETHODIMP nsDragService::NativeDataToTransferable( PCSZ pszText,
                                                PCSZ pszTitle, bool isUrl)
{
  nsresult rv = NS_ERROR_FAILURE;
    // the transferable should have been created on DragEnter
 if (!mSourceDataItems)
    return rv;

  nsCOMPtr<nsISupports> genericItem;
  mSourceDataItems->GetElementAt(0, getter_AddRefs(genericItem));
  nsCOMPtr<nsITransferable> trans (do_QueryInterface(genericItem));
  if (!trans)
    return rv;

    // remove invalid flavors
  if (!isUrl) {
    trans->RemoveDataFlavor(kURLMime);
    trans->RemoveDataFlavor(kHTMLMime);
  }

    // if there's no text, exit - but first see if we have the title of
    // a Url that needs to be rendered;  if so, stash it for later use
  if (!pszText || !*pszText) {
    if (isUrl && pszTitle && *pszTitle) {
      nsXPIDLString outTitle;
      if (CodepageToUnicode(nsDependentCString(pszTitle),
                                               getter_Copies(outTitle))) {
        nsCOMPtr<nsISupportsString> urlPrimitive(
                        do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
        if (urlPrimitive ) {
          urlPrimitive->SetData(outTitle);
          trans->SetTransferData(kURLDescriptionMime, urlPrimitive,
                                 2*outTitle.Length());
        }
      }
    }
    return NS_OK;
  }

  nsXPIDLString outText;
  if (!CodepageToUnicode(nsDependentCString(pszText), getter_Copies(outText)))
    return rv;

  if (isUrl) {

      // if no title was supplied, see if it was stored in the transferable
    nsXPIDLString outTitle;
    if (pszTitle && *pszTitle) {
      if (!CodepageToUnicode(nsDependentCString(pszTitle),
                             getter_Copies(outTitle)))
        return rv;
    }
    else {
      PRUint32 len;
      nsCOMPtr<nsISupports> genericData;
      if (NS_SUCCEEDED(trans->GetTransferData(kURLDescriptionMime,
                                   getter_AddRefs(genericData), &len))) {
        nsCOMPtr<nsISupportsString> strObject(do_QueryInterface(genericData));
        if (strObject)
          strObject->GetData(outTitle);
      }
    }

      // construct the Url flavor
    nsCOMPtr<nsISupportsString> urlPrimitive(
                            do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
    if (urlPrimitive ) {
      if (outTitle.IsEmpty()) {
        urlPrimitive->SetData(outText);
        trans->SetTransferData(kURLMime, urlPrimitive, 2*outText.Length());
      }
      else {
        nsString urlStr( outText + NS_LITERAL_STRING("\n") + outTitle);
        urlPrimitive->SetData(urlStr);
        trans->SetTransferData(kURLMime, urlPrimitive, 2*urlStr.Length());
      }
      rv = NS_OK;
    }

      // construct the HTML flavor - for supported graphics,
      // use an IMG tag, for all others create a link
    nsCOMPtr<nsISupportsString> htmlPrimitive(
                            do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
    if (htmlPrimitive ) {
      nsString htmlStr;
      nsCOMPtr<nsIURI> uri;

      rv = NS_ERROR_FAILURE;
      if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), pszText))) {
        nsCOMPtr<nsIURL> url (do_QueryInterface(uri));
        if (url) {
          nsCAutoString extension;
          url->GetFileExtension(extension);
          if (!extension.IsEmpty()) {
            if (extension.LowerCaseEqualsLiteral("gif") ||
                extension.LowerCaseEqualsLiteral("jpg") ||
                extension.LowerCaseEqualsLiteral("png") ||
                extension.LowerCaseEqualsLiteral("jpeg"))
              rv = NS_OK;
          }
        }
      }

      if (NS_SUCCEEDED(rv))
        htmlStr.Assign(NS_LITERAL_STRING("<img src=\"") +
                       outText +
                       NS_LITERAL_STRING("\" alt=\"") +
                       outTitle +
                       NS_LITERAL_STRING("\"/>") );
      else
        htmlStr.Assign(NS_LITERAL_STRING("<a href=\"") +
                       outText +
                       NS_LITERAL_STRING("\">") +
                       (outTitle.IsEmpty() ? outText : outTitle) +
                       NS_LITERAL_STRING("</a>") );

      htmlPrimitive->SetData(htmlStr);
      trans->SetTransferData(kHTMLMime, htmlPrimitive, 2*htmlStr.Length());
      rv = NS_OK;
    }
  }

    // add the Text flavor
  nsCOMPtr<nsISupportsString> textPrimitive(
                            do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
  if (textPrimitive ) {
    textPrimitive->SetData(nsDependentString(outText));
    trans->SetTransferData(kUnicodeMime, textPrimitive, 2*outText.Length());
    rv = NS_OK;
  }

    // return OK if we put anything in the transferable
  return rv;
}

// --------------------------------------------------------------------------
// Helper functions
// --------------------------------------------------------------------------

// currently, the same filename is used for every render request;
// it is deleted when the drag session ends

nsresult RenderToOS2File( PDRAGITEM pditem, HWND hwnd)
{
  nsresult rv = NS_ERROR_FAILURE;
  nsXPIDLCString fileName;

  if (NS_SUCCEEDED(GetTempFileName(getter_Copies(fileName)))) {
    const char * pszRMF;
    if (DrgVerifyRMF(pditem, "DRM_OS2FILE", "DRF_TEXT"))
      pszRMF = OS2FILE_TXTRMF;
    else
      pszRMF = OS2FILE_UNKRMF;

    rv = RequestRendering( pditem, hwnd, pszRMF, fileName.get());
  }

  return rv;
}

// --------------------------------------------------------------------------

// return a buffer with the rendered file's Url or contents

nsresult RenderToOS2FileComplete(PDRAGTRANSFER pdxfer, USHORT usResult,
                                 bool content, char** outText)
{
  nsresult rv = NS_ERROR_FAILURE;

    // for now, override content flag & always return content
  content = true;

  if (usResult & DMFL_RENDEROK) {
    if (NS_SUCCEEDED(GetAtom( pdxfer->hstrRenderToName, &gTempFile))) {
      if (content)
        rv = GetFileContents(gTempFile, outText);
      else {
        nsCOMPtr<nsILocalFile> file;
        if (NS_SUCCEEDED(NS_NewNativeLocalFile(nsDependentCString(gTempFile),
                                         true, getter_AddRefs(file)))) {
          nsCAutoString textStr;
          NS_GetURLSpecFromFile(file, textStr);
          if (!textStr.IsEmpty()) {
            *outText = ToNewCString(textStr);
            rv = NS_OK;
          }
        }
      }
    }
  }
    // gTempFile will be deleted when ExitSession() is called

  return rv;
}

// --------------------------------------------------------------------------

// DTShare uses 1mb of uncommitted named-shared memory
// (next time I'll do it differently - rw)

nsresult RenderToDTShare( PDRAGITEM pditem, HWND hwnd)
{
  nsresult rv;
  void *   pMem;

#ifdef MOZ_OS2_HIGH_MEMORY
  APIRET rc = DosAllocSharedMem( &pMem, DTSHARE_NAME, 0x100000,
                                 PAG_WRITE | PAG_READ | OBJ_ANY);
  if (rc != NO_ERROR &&
      rc != ERROR_ALREADY_EXISTS) { // Did the kernel handle OBJ_ANY?
    // Try again without OBJ_ANY and if the first failure was not caused
    // by OBJ_ANY then we will get the same failure, else we have taken
    // care of pre-FP13 systems where the kernel couldn't handle it.
    rc = DosAllocSharedMem( &pMem, DTSHARE_NAME, 0x100000,
                            PAG_WRITE | PAG_READ);
  }
#else
  APIRET rc = DosAllocSharedMem( &pMem, DTSHARE_NAME, 0x100000,
                                 PAG_WRITE | PAG_READ);
#endif

  if (rc == ERROR_ALREADY_EXISTS)
    rc = DosGetNamedSharedMem( &pMem, DTSHARE_NAME,
                               PAG_WRITE | PAG_READ);
  if (rc)
    rv = NS_ERROR_FAILURE;
  else
    rv = RequestRendering( pditem, hwnd, DTSHARE_RMF, DTSHARE_NAME);

  return rv;
}

// --------------------------------------------------------------------------

// return a buffer with the rendered text

nsresult RenderToDTShareComplete(PDRAGTRANSFER pdxfer, USHORT usResult,
                                 char** outText)
{
  nsresult rv = NS_ERROR_FAILURE;
  void * pMem;
  char * pszText = 0;

  APIRET rc = DosGetNamedSharedMem( &pMem, DTSHARE_NAME, PAG_WRITE | PAG_READ);

  if (!rc) {
    if (usResult & DMFL_RENDEROK) {
      pszText = (char*)nsMemory::Alloc( ((ULONG*)pMem)[0] + 1);
      if (pszText) {
        strcpy(pszText, &((char*)pMem)[sizeof(ULONG)] );
        RemoveCarriageReturns(pszText);
        *outText = pszText;
        rv = NS_OK;
      }
    }
      // using DosGetNamedSharedMem() on memory we allocated appears
      // to increment its usage ctr, so we have to free it 2x
    DosFreeMem(pMem);
    DosFreeMem(pMem);
  }

  return rv;
}

// --------------------------------------------------------------------------

// a generic request dispatcher

nsresult RequestRendering( PDRAGITEM pditem, HWND hwnd, PCSZ pRMF, PCSZ pName)
{
  PDRAGTRANSFER pdxfer = DrgAllocDragtransfer( 1);
  if (!pdxfer)
    return NS_ERROR_FAILURE;
 
  pdxfer->cb = sizeof(DRAGTRANSFER);
  pdxfer->hwndClient = hwnd;
  pdxfer->pditem = pditem;
  pdxfer->hstrSelectedRMF = DrgAddStrHandle( pRMF);
  pdxfer->hstrRenderToName = 0;
  pdxfer->ulTargetInfo = pditem->ulItemID;
  pdxfer->usOperation = (USHORT)DO_COPY;
  pdxfer->fsReply = 0;
 
    // send the msg before setting a render-to name
  if (pditem->fsControl & DC_PREPAREITEM)
    DrgSendTransferMsg( pditem->hwndItem, DM_RENDERPREPARE, (MPARAM)pdxfer, 0);
 
  pdxfer->hstrRenderToName = DrgAddStrHandle( pName);
 
    // send the msg after setting a render-to name
  if ((pditem->fsControl & (DC_PREPARE | DC_PREPAREITEM)) == DC_PREPARE)
    DrgSendTransferMsg( pditem->hwndItem, DM_RENDERPREPARE, (MPARAM)pdxfer, 0);
 
    // ask the source to render the selected item
  if (!DrgSendTransferMsg( pditem->hwndItem, DM_RENDER, (MPARAM)pdxfer, 0))
    return NS_ERROR_FAILURE;
 
  return NS_OK;
}

// --------------------------------------------------------------------------

// return a ptr to a buffer containing the text associated
// with a drag atom;  the caller frees the buffer

nsresult GetAtom( ATOM aAtom, char** outText)
{
  nsresult rv = NS_ERROR_FAILURE;

  ULONG ulInLength = DrgQueryStrNameLen(aAtom);
  if (ulInLength) {
    char* pszText = (char*)nsMemory::Alloc(++ulInLength);
    if (pszText) {
      DrgQueryStrName(aAtom, ulInLength, pszText);
      RemoveCarriageReturns(pszText);
      *outText = pszText;
      rv = NS_OK;
    }
  }
  return rv;
}

// --------------------------------------------------------------------------

// return a ptr to a buffer containing the file path specified
// in the dragitem;  the caller frees the buffer

nsresult GetFileName(PDRAGITEM pditem, char** outText)
{
  nsresult rv = NS_ERROR_FAILURE;
  ULONG cntCnr = DrgQueryStrNameLen(pditem->hstrContainerName);
  ULONG cntSrc = DrgQueryStrNameLen(pditem->hstrSourceName);

  char* pszText = (char*)nsMemory::Alloc(cntCnr+cntSrc+1);
  if (pszText) {
    DrgQueryStrName(pditem->hstrContainerName, cntCnr+1, pszText);
    DrgQueryStrName(pditem->hstrSourceName, cntSrc+1, &pszText[cntCnr]);
    pszText[cntCnr+cntSrc] = 0;
    *outText = pszText;
    rv = NS_OK;
  }
  return rv;
}

// --------------------------------------------------------------------------

// read the file;  if successful, return a ptr to its contents;
// the caller frees the buffer

nsresult GetFileContents(PCSZ pszPath, char** outText)
{
  nsresult rv = NS_ERROR_FAILURE;
  char* pszText = 0;

  if (pszPath) {
    FILE *fp = fopen(pszPath, "r");
    if (fp) {
      fseek(fp, 0, SEEK_END);
      ULONG filesize = ftell(fp);
      fseek(fp, 0, SEEK_SET);
      if (filesize > 0) {
        size_t readsize = (size_t)filesize;
        pszText = (char*)nsMemory::Alloc(readsize+1);
        if (pszText) {
          readsize = fread((void *)pszText, 1, readsize, fp);
          if (readsize) {
            pszText[readsize] = '\0';
            RemoveCarriageReturns(pszText);
            *outText = pszText;
            rv = NS_OK;
          }
          else {
            nsMemory::Free(pszText);
            pszText = 0;
          }
        }
      }
      fclose(fp);
    }
  }

  return rv;
}

// --------------------------------------------------------------------------

// currently, this returns the same path & filename every time

nsresult GetTempFileName(char** outText)
{
  char * pszText = (char*)nsMemory::Alloc(CCHMAXPATH);
  if (!pszText)
    return NS_ERROR_FAILURE;

  const char * pszPath;
  if (!DosScanEnv("TEMP", &pszPath) || !DosScanEnv("TMP", &pszPath))
    strcpy(pszText, pszPath);
  else
    if (DosQueryPathInfo(".\\.", FIL_QUERYFULLNAME, pszText, CCHMAXPATH))
      pszText[0] = 0;

  strcat(pszText, "\\");
  strcat(pszText, OS2FILE_NAME);
  *outText = pszText;

  return NS_OK;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------

// set the file's .TYPE and .SUBJECT EAs;  since this is non-critical
// (though highly desirable), errors aren't reported

void SaveTypeAndSource(nsILocalFile *file, nsIDOMDocument *domDoc,
                       PCSZ pszType)
{
  if (!file)
    return;

  nsCOMPtr<nsILocalFileOS2> os2file(do_QueryInterface(file));
  if (!os2file ||
      NS_FAILED(os2file->SetFileTypes(nsDependentCString(pszType))))
    return;

  // since the filetype has to be saved, this function
  // may be called even if there isn't any document
  if (!domDoc)
    return;

  nsCOMPtr<nsIDocument> doc(do_QueryInterface(domDoc));
  if (!doc)
    return;

  // this gets the top-level content URL when frames are used;
  // when nextDoc is zero, currDoc is the browser window, and
  // prevDoc points to the content;
  // note:  neither GetParentDocument() nor GetDocumentURI()
  // refcount the pointers they return, so nsCOMPtr isn't needed
  nsIDocument *prevDoc;
  nsIDocument *currDoc = doc;
  nsIDocument *nextDoc = doc;
  do {
    prevDoc = currDoc;
    currDoc = nextDoc;
    nextDoc = currDoc->GetParentDocument();
  } while (nextDoc);

  nsIURI* srcUri = prevDoc->GetDocumentURI();
  if (!srcUri)
    return;

  // identifying content as coming from chrome is none too useful...
  bool ignore = false;
  srcUri->SchemeIs("chrome", &ignore);
  if (ignore)
    return;

  nsCAutoString url;
  srcUri->GetSpec(url);
  os2file->SetFileSource(url);

  return;
}

// --------------------------------------------------------------------------

// to do:  this needs to take into account the current page's encoding
// if it is different than the PM codepage

int UnicodeToCodepage(const nsAString& aString, char **aResult)
{
  nsAutoCharBuffer buffer;
  PRInt32 bufLength;
  WideCharToMultiByte(0, PromiseFlatString(aString).get(), aString.Length(),
                      buffer, bufLength);
  *aResult = ToNewCString(nsDependentCString(buffer.Elements()));
  return bufLength;
}

// --------------------------------------------------------------------------

int CodepageToUnicode(const nsACString& aString, PRUnichar **aResult)
{
  nsAutoChar16Buffer buffer;
  PRInt32 bufLength;
  MultiByteToWideChar(0, PromiseFlatCString(aString).get(),
                      aString.Length(), buffer, bufLength);
  *aResult = ToNewUnicode(nsDependentString(buffer.Elements()));
  return bufLength;
}

// --------------------------------------------------------------------------

// removes carriage returns in-place;  it should only be used on
// raw text buffers that haven't been assigned to a string object

void RemoveCarriageReturns(char * pszText)
{
  ULONG  cnt;
  char * next;
  char * source;
  char * target;

  target = strchr(pszText, 0x0d);
  if (!target)
    return;

  source = target + 1;

  while ((next = strchr(source, 0x0d)) != 0) {

    cnt = next - source;
    memcpy(target, source, cnt);
    target += cnt;
    source = next + 1;

  }

  strcpy(target, source);
  return;
}

// --------------------------------------------------------------------------
