text: /*
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; either version 2
   * of the License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
   *
   * The Original Code is Copyright (C) 2012 by the Blender Foundation.
   * All rights reserved.
   */
  
  /** \file
   * \ingroup GHOST
   */
  
  #include "GHOST_DropTargetX11.h"
  #include "GHOST_Debug.h"
  
  #include <assert.h>
  #include <ctype.h>
  #include <stdio.h>
  #include <string.h>
  
  bool GHOST_DropTargetX11::m_xdndInitialized = false;
  DndClass GHOST_DropTargetX11::m_dndClass;
  Atom *GHOST_DropTargetX11::m_dndTypes = NULL;
  Atom *GHOST_DropTargetX11::m_dndActions = NULL;
  const char *GHOST_DropTargetX11::m_dndMimeTypes[] = {
      "url/url", "text/uri-list", "text/plain", "application/octet-stream"};
  int GHOST_DropTargetX11::m_refCounter = 0;
  
  #define dndTypeURLID 0
  #define dndTypeURIListID 1
  #define dndTypePlainTextID 2
  #define dndTypeOctetStreamID 3
  
  #define dndTypeURL m_dndTypes[dndTypeURLID]
  #define dndTypeURIList m_dndTypes[dndTypeURIListID]
  #define dndTypePlainText m_dndTypes[dndTypePlainTextID]
  #define dndTypeOctetStream m_dndTypes[dndTypeOctetStreamID]
  
  void GHOST_DropTargetX11::Initialize(void)
  {
    Display *display = m_system->getXDisplay();
    int dndTypesCount = sizeof(m_dndMimeTypes) / sizeof(char *);
    int counter;
  
    xdnd_init(&m_dndClass, display);
  
    m_dndTypes = new Atom[dndTypesCount + 1];
    XInternAtoms(display, (char **)m_dndMimeTypes, dndTypesCount, 0, m_dndTypes);
    m_dndTypes[dndTypesCount] = 0;
  
    m_dndActions = new Atom[8];
    counter = 0;
  
    m_dndActions[counter++] = m_dndClass.XdndActionCopy;
    m_dndActions[counter++] = m_dndClass.XdndActionMove;
  
  #if 0 /* Not supported yet */
    dndActions[counter++] = dnd->XdndActionLink;
    dndActions[counter++] = dnd->XdndActionAsk;
    dndActions[counter++] = dnd->XdndActionPrivate;
    dndActions[counter++] = dnd->XdndActionList;
    dndActions[counter++] = dnd->XdndActionDescription;
  #endif
  
    m_dndActions[counter++] = 0;
  }
  
  void GHOST_DropTargetX11::Uninitialize(void)
  {
    xdnd_shut(&m_dndClass);
  
    delete[] m_dndActions;
    delete[] m_dndTypes;
  }
  
  GHOST_DropTargetX11::GHOST_DropTargetX11(GHOST_WindowX11 *window, GHOST_SystemX11 *system)
      : m_window(window), m_system(system)
  {
    if (!m_xdndInitialized) {
      Initialize();
      m_xdndInitialized = true;
      GHOST_PRINT("XDND initialized\n");
    }
  
    Window wnd = window->getXWindow();
  
    xdnd_set_dnd_aware(&m_dndClass, wnd, 0);
    xdnd_set_type_list(&m_dndClass, wnd, m_dndTypes);
  
    m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
    m_refCounter++;
  }
  
  GHOST_DropTargetX11::~GHOST_DropTargetX11()
  {
    m_refCounter--;
    if (m_refCounter == 0) {
      Uninitialize();
      m_xdndInitialized = false;
      GHOST_PRINT("XDND uninitialized\n");
    }
  }
  
  /* based on a code from Saul Rennison
   * http://stackoverflow.com/questions/2673207/c-c-url-decode-library */
  
  typedef enum DecodeState_e {
    STATE_SEARCH = 0,  ///< searching for an ampersand to convert
    STATE_CONVERTING   ///< convert the two proceeding characters from hex
  } DecodeState_e;
  
  void GHOST_DropTargetX11::UrlDecode(char *decodedOut, int bufferSize, const char *encodedIn)
  {
    unsigned int i;
    unsigned int len = strlen(encodedIn);
    DecodeState_e state = STATE_SEARCH;
    int j;
    unsigned int asciiCharacter;
    char tempNumBuf[3] = {0};
    bool bothDigits = true;
  
    memset(decodedOut, 0, bufferSize);
  
    for (i = 0; i < len; ++i) {
      switch (state) {
        case STATE_SEARCH:
          if (encodedIn[i] != '%') {
            strncat(decodedOut, &encodedIn[i], 1);
            assert(strlen(decodedOut) < bufferSize);
            break;
          }
  
          /* We are now converting */
          state = STATE_CONVERTING;
          break;
  
        case STATE_CONVERTING:
          bothDigits = true;
  
          /* Create a buffer to hold the hex. For example, if %20, this
           * buffer would hold 20 (in ASCII) */
          memset(tempNumBuf, 0, sizeof(tempNumBuf));
  
          /* Conversion complete (i.e. don't convert again next iter) */
          state = STATE_SEARCH;
  
          strncpy(tempNumBuf, &encodedIn[i], 2);
  
          /* Ensure both characters are hexadecimal */
  
          for (j = 0; j < 2; ++j) {
            if (!isxdigit(tempNumBuf[j]))
              bothDigits = false;
          }
  
          if (!bothDigits)
            break;
  
          /* Convert two hexadecimal characters into one character */
          sscanf(tempNumBuf, "%x", &asciiCharacter);
  
          /* Ensure we aren't going to overflow */
          assert(strlen(decodedOut) < bufferSize);
  
          /* Concatenate this character onto the output */
          strncat(decodedOut, (char *)&asciiCharacter, 1);
  
          /* Skip the next character */
          i++;
          break;
      }
    }
  }
  
  char *GHOST_DropTargetX11::FileUrlDecode(char *fileUrl)
  {
    if (strncmp(fileUrl, "file://", 7) == 0) {
      /* assume one character of encoded URL can be expanded to 4 chars max */
      int decodedSize = 4 * strlen(fileUrl) + 1;
      char *decodedPath = (char *)malloc(decodedSize);
  
      UrlDecode(decodedPath, decodedSize, fileUrl + 7);
  
      return decodedPath;
    }
  
    return NULL;
  }
  
  void *GHOST_DropTargetX11::getURIListGhostData(unsigned char *dropBuffer, int dropBufferSize)
  {
    GHOST_TStringArray *strArray = NULL;
    int totPaths = 0, curLength = 0;
  
    /* Count total number of file paths in buffer. */
    for (int i = 0; i <= dropBufferSize; i++) {
      if (dropBuffer[i] == 0 || dropBuffer[i] == '\n' || dropBuffer[i] == '\r') {
        if (curLength) {
          totPaths++;
          curLength = 0;
        }
      }
      else
        curLength++;
    }
  
    strArray = (GHOST_TStringArray *)malloc(sizeof(GHOST_TStringArray));
    strArray->count = 0;
    strArray->strings = (GHOST_TUns8 **)malloc(totPaths * sizeof(GHOST_TUns8 *));
  
    curLength = 0;
    for (int i = 0; i <= dropBufferSize; i++) {
      if (dropBuffer[i] == 0 || dropBuffer[i] == '\n' || dropBuffer[i] == '\r') {
        if (curLength) {
          char *curPath = (char *)malloc(curLength + 1);
          char *decodedPath;
  
          strncpy(curPath, (char *)dropBuffer + i - curLength, curLength);
          curPath[curLength] = 0;
  
          decodedPath = FileUrlDecode(curPath);
          if (decodedPath) {
            strArray->strings[strArray->count] = (GHOST_TUns8 *)decodedPath;
            strArray->count++;
          }
  
          free(curPath);
          curLength = 0;
        }
      }
      else
        curLength++;
    }
  
    return strArray;
  }
  
  void *GHOST_DropTargetX11::getGhostData(Atom dropType,
                                          unsigned char *dropBuffer,
                                          int dropBufferSize)
  {
    void *data = NULL;
    unsigned char *tmpBuffer = (unsigned char *)malloc(dropBufferSize + 1);
    bool needsFree = true;
  
    /* ensure NULL-terminator */
    memcpy(tmpBuffer, dropBuffer, dropBufferSize);
    tmpBuffer[dropBufferSize] = 0;
  
    if (dropType == dndTypeURIList) {
      m_draggedObjectType = GHOST_kDragnDropTypeFilenames;
      data = getURIListGhostData(tmpBuffer, dropBufferSize);
    }
    else if (dropType == dndTypeURL) {
      /* need to be tested */
      char *decodedPath = FileUrlDecode((char *)tmpBuffer);
  
      if (decodedPath) {
        m_draggedObjectType = GHOST_kDragnDropTypeString;
        data = decodedPath;
      }
    }
    else if (dropType == dndTypePlainText || dropType == dndTypeOctetStream) {
      m_draggedObjectType = GHOST_kDragnDropTypeString;
      data = tmpBuffer;
      needsFree = false;
    }
    else {
      m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
    }
  
    if (needsFree)
      free(tmpBuffer);
  
    return data;
  }
  
  bool GHOST_DropTargetX11::GHOST_HandleClientMessage(XEvent *event)
  {
    Atom dropType;
    unsigned char *dropBuffer;
    int dropBufferSize, dropX, dropY;
  
    if (xdnd_get_drop(m_system->getXDisplay(),
                      event,
                      m_dndTypes,
                      m_dndActions,
                      &dropBuffer,
                      &dropBufferSize,
                      &dropType,
                      &dropX,
                      &dropY)) {
      void *data = getGhostData(dropType, dropBuffer, dropBufferSize);
  
      if (data)
        m_system->pushDragDropEvent(
            GHOST_kEventDraggingDropDone, m_draggedObjectType, m_window, dropX, dropY, data);
  
      free(dropBuffer);
  
      m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
  
      return true;
    }
  
    return false;
  }
  