/** \file
* \brief Tabs Control
*
* See Copyright Notice in "iup.h"
*/

#include <windows.h>
#include <commctrl.h>
#include <wincodec.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdarg.h>

#include "iup.h"
#include "iupcbs.h"

#include "iup_object.h"
#include "iup_attrib.h"
#include "iup_str.h"
#include "iup_drv.h"
#include "iup_drvfont.h"
#include "iup_tabs.h"
#include "iup_image.h"
#include "iup_array.h"
#include "iup_assert.h"

#include "iupwin_drv.h"
#include "iupwin_handle.h"
#include "iupwin_draw.h"
#include "iupwin_info.h"
#include "iupwin_str.h"


#ifndef WS_EX_COMPOSITED
#define WS_EX_COMPOSITED        0x02000000L
#endif

#define ITABS_CLOSE_SIZE 12

static void winTabsInitializeCloseImage(void)
{
  Ihandle *image_close;

  unsigned char img_close[ITABS_CLOSE_SIZE * ITABS_CLOSE_SIZE] =
  {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0,
    0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0,
    0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  };

  image_close = IupImage(ITABS_CLOSE_SIZE, ITABS_CLOSE_SIZE, img_close);
  IupSetAttribute(image_close, "0", "BGCOLOR");
  IupSetAttribute(image_close, "1", "0 0 0");
  IupSetHandle("IMGCLOSE", image_close);

  image_close = IupImage(ITABS_CLOSE_SIZE, ITABS_CLOSE_SIZE, img_close);
  IupSetAttribute(image_close, "0", "BGCOLOR");
  IupSetStrAttribute(image_close, "1", IupGetGlobal("TXTHLCOLOR"));
  IupSetHandle("IMGCLOSEHIGH", image_close);
}

static void winTabsInitVisibleArray(Iarray* visible_array, int count)
{
  int old_count = iupArrayCount(visible_array);
  if (old_count != count)
  {
    iupArrayRemove(visible_array, 0, old_count);
    iupArrayAdd(visible_array, count);
  }

  /* all visible by default */
  {
    int pos;
    int* visible_array_array_data = (int*)iupArrayGetData(visible_array);
    for (pos = 0; pos < count; pos++)
      visible_array_array_data[pos] = 1;
  }
}

static Iarray* winTabsGetVisibleArray(Ihandle* ih)
{
  Iarray* visible_array = (Iarray*)iupAttribGet(ih, "_IUPWIN_VISIBLEARRAY");

  if (!visible_array)
  {
    int count = IupGetChildCount(ih);

    /* create the array if it does not exist */
    visible_array = iupArrayCreate(50, sizeof(int));
    iupAttribSet(ih, "_IUPWIN_VISIBLEARRAY", (char*)visible_array);

    winTabsInitVisibleArray(visible_array, count);
  }

  return visible_array;
}

static int winTabsHasInVisible(Iarray* visible_array)
{
  int i;
  int count = iupArrayCount(visible_array);
  int* visible_array_array_data = (int*)iupArrayGetData(visible_array);

  for (i = 0; i < count; i++)
  {
    if (visible_array_array_data[i] == 0)
      return 1;
  }

  return 0;
}

static void winTabsSetVisibleArrayItem(Ihandle* ih, int pos, int visible)
{
  /* if set to visible and all are visible, just return */
  if (visible && !ih->data->has_invisible)
    return;
  else
  {
    Iarray* visible_array = winTabsGetVisibleArray(ih);
    int* visible_array_array_data = (int*)iupArrayGetData(visible_array);

    /* first invisible init array */
    if (!visible && !ih->data->has_invisible)
    {
      int count = IupGetChildCount(ih);
      winTabsInitVisibleArray(visible_array, count);
    }

    visible_array_array_data[pos] = visible;

    if (!visible)
      ih->data->has_invisible = 1;
    else
      ih->data->has_invisible = winTabsHasInVisible(visible_array);
  }
}

static void winTabsInsertVisibleArrayItem(Ihandle* ih, int pos)
{
  if (ih->data->has_invisible)
  {
    Iarray* visible_array = winTabsGetVisibleArray(ih);
    iupArrayInsert(visible_array, pos, 1);
    winTabsSetVisibleArrayItem(ih, pos, 1);
  }
}

static void winTabsDeleteVisibleArrayItem(Ihandle* ih, int pos)
{
  if (ih->data->has_invisible)
  {
    Iarray* visible_array = winTabsGetVisibleArray(ih);
    iupArrayRemove(visible_array, pos, 1);
    ih->data->has_invisible = winTabsHasInVisible(visible_array);
  }
}

static int winTabsIsTabVisible(Ihandle* ih, int pos)
{
  if (ih->data->has_invisible)
  {
    Iarray* visible_array = winTabsGetVisibleArray(ih);
    int* visible_array_array_data = (int*)iupArrayGetData(visible_array);
    return visible_array_array_data[pos];
  }
  else
    return 1;
}

static int winTabsGetInsertPos(Ihandle* ih, int pos)
{
  if (ih->data->has_invisible)
  {
    Iarray* visible_array = winTabsGetVisibleArray(ih);
    int* visible_array_array_data = (int*)iupArrayGetData(visible_array);
    int i, p;

    for (i = 0, p = 0; i < pos; i++)
    {
      if (visible_array_array_data[i] != 0)
        p++;
    }

    return p;
  }
  else
    return pos;
}

static int winTabsPosFixToWin(Ihandle* ih, int pos)
{
  if (ih->data->has_invisible)
  {
    Iarray* visible_array = winTabsGetVisibleArray(ih);
    int* visible_array_array_data = (int*)iupArrayGetData(visible_array);
    int i, p;

    if (!visible_array_array_data[pos])
      return -1;

    for (i = 0, p = 0; i<pos; i++)
    {
      if (visible_array_array_data[i] != 0)
        p++;
    }

    return p;
  }
  else
    return pos;
}

static int winTabsPosFixFromWin(Ihandle* ih, int p)
{
  if (ih->data->has_invisible)
  {
    Iarray* visible_array = winTabsGetVisibleArray(ih);
    int* visible_array_array_data = (int*)iupArrayGetData(visible_array);
    int pos, pp, count = iupArrayCount(visible_array);

    for (pos = 0, pp = -1; pos<count; pos++)
    {
      if (visible_array_array_data[pos] != 0)
        pp++;

      if (pp == p)
        return pos;
    }

    iupERROR("IupTabs Error. Invalid internal visible state.");
    return 0;  /* INTERNAL ERROR: must always be found */
  }
  else
    return p;
}

static int winTabsGetPageWindowPos(Ihandle* ih, HWND tab_container)
{
  TCITEM tie;
  int p, num_tabs;

  num_tabs = (int)SendMessage(ih->handle, TCM_GETITEMCOUNT, 0, 0);
  tie.mask = TCIF_PARAM;

  for (p = 0; p<num_tabs; p++)
  {
    SendMessage(ih->handle, TCM_GETITEM, p, (LPARAM)&tie);
    if (tab_container == (HWND)tie.lParam)
      return p;
  }

  return -1;
}

IUP_SDK_API int iupdrvTabsExtraMargin(void)
{
  if (iupwin_comctl32ver6 && iupwinIsWin10OrNew())
    return 2;
  return 0;
}

IUP_SDK_API int iupdrvTabsExtraDecor(Ihandle* ih)
{
  (void)ih;
  return 0;
}

IUP_SDK_API int iupdrvTabsGetLineCountAttrib(Ihandle* ih)
{
  return (int)SendMessage(ih->handle, TCM_GETROWCOUNT, 0, 0);
}

static HWND winTabsGetPageWindow(Ihandle* ih, int pos)
{
  int p = winTabsPosFixToWin(ih, pos);
  if (p >= 0)
  {
    TCITEM tie;
    tie.mask = TCIF_PARAM;
    SendMessage(ih->handle, TCM_GETITEM, p, (LPARAM)&tie);
    return (HWND)tie.lParam;
  }
  else
    return NULL;  /* invisible */
}

IUP_SDK_API void iupdrvTabsSetCurrentTab(Ihandle* ih, int pos)
{
  int p = winTabsPosFixToWin(ih, pos);
  if (p >= 0)
  {
    int curr_pos = iupdrvTabsGetCurrentTab(ih);
    HWND tab_container = winTabsGetPageWindow(ih, curr_pos);
    if (tab_container)
      ShowWindow(tab_container, SW_HIDE);

    SendMessage(ih->handle, TCM_SETCURSEL, p, 0);

    tab_container = winTabsGetPageWindow(ih, pos);
    if (tab_container)
      ShowWindow(tab_container, SW_SHOW);
  }
}

IUP_SDK_API int iupdrvTabsGetCurrentTab(Ihandle* ih)
{
  return winTabsPosFixFromWin(ih, (int)SendMessage(ih->handle, TCM_GETCURSEL, 0, 0));
}

IUP_SDK_API void iupdrvTabsGetTabSize(Ihandle* ih, const char* tab_title, const char* tab_image, int* tab_width, int* tab_height)
{
  int width = 0;
  int height = 0;
  int text_width = 0;
  int text_height = 0;
  int is_vertical = (ih->data->orientation == ITABS_VERTICAL);

  /* Measure text dimensions */
  if (tab_title)
  {
    text_width = iupdrvFontGetStringWidth(ih, tab_title);
    iupdrvFontGetCharSize(ih, NULL, &text_height);

    /* LEFT/RIGHT tabs have vertical text (rotated 90 degrees) */
    if (is_vertical)
    {
      width = text_height;   /* text rotated: tab width = text height */
      height = text_width;   /* text rotated: tab height = text width */
    }
    else
    {
      width = text_width;    /* horizontal text */
      height = text_height;
    }
  }

  if (tab_image)
  {
    void* img = iupImageGetImage(tab_image, ih, 0, NULL);
    if (img)
    {
      int img_w, img_h;
      iupdrvImageGetInfo(img, &img_w, &img_h, NULL);
      iupTabsScaleImageSize(ih, img_w, img_h, &img_w, &img_h);

      if (is_vertical)
      {
        /* For vertical tabs, image is above/below the vertical text */
        height += img_h;
        if (tab_title)
          height += 3;  /* spacing between icon and text */
        if (img_w > width)
          width = img_w;
      }
      else
      {
        /* For horizontal tabs, image is left/right of the text */
        width += img_w;
        if (tab_title)
          width += 3;  /* spacing between icon and text */
        if (img_h > height)
          height = img_h;
      }
    }
  }

  if (is_vertical)
  {
    width += 12;   /* padding around rotated text width */
    height += 30;  /* padding around rotated text height (which is text width) */
  }
  else
  {
    width += 30;   /* padding around text width */
    height += 12;  /* padding around text height */
  }

  if (tab_width) *tab_width = width;
  if (tab_height) *tab_height = height;
}

/* Fant scaler: high-quality downscale that keeps the premultiplied alpha */
static HBITMAP winTabsCreateScaledBitmap(HBITMAP src, int src_w, int src_h, int dst_w, int dst_h)
{
  IWICImagingFactory* factory = NULL;
  IWICBitmap* wic_bmp = NULL;
  IWICBitmapScaler* scaler = NULL;
  BITMAPINFO bmi;
  void* dst_bits = NULL;
  HBITMAP dst = NULL;
  HDC screen;
  HRESULT hr;
  (void)src_w; (void)src_h;

  hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, &IID_IWICImagingFactory, (void**)&factory);
  if (FAILED(hr)) goto cleanup;

  hr = IWICImagingFactory_CreateBitmapFromHBITMAP(factory, src, NULL, WICBitmapUsePremultipliedAlpha, &wic_bmp);
  if (FAILED(hr)) goto cleanup;

  hr = IWICImagingFactory_CreateBitmapScaler(factory, &scaler);
  if (FAILED(hr)) goto cleanup;

  hr = IWICBitmapScaler_Initialize(scaler, (IWICBitmapSource*)wic_bmp, dst_w, dst_h, WICBitmapInterpolationModeFant);
  if (FAILED(hr)) goto cleanup;

  screen = GetDC(NULL);
  ZeroMemory(&bmi, sizeof(bmi));
  bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bmi.bmiHeader.biWidth = dst_w;
  bmi.bmiHeader.biHeight = -dst_h;  /* top-down to match WIC output */
  bmi.bmiHeader.biPlanes = 1;
  bmi.bmiHeader.biBitCount = 32;
  bmi.bmiHeader.biCompression = BI_RGB;
  dst = CreateDIBSection(screen, &bmi, DIB_RGB_COLORS, &dst_bits, NULL, 0);
  ReleaseDC(NULL, screen);

  if (dst)
  {
    UINT stride = (UINT)dst_w * 4;
    hr = IWICBitmapScaler_CopyPixels(scaler, NULL, stride, stride * dst_h, (BYTE*)dst_bits);
    if (FAILED(hr)) { DeleteObject(dst); dst = NULL; }
  }

cleanup:
  if (scaler) IWICBitmapScaler_Release(scaler);
  if (wic_bmp) IWICBitmap_Release(wic_bmp);
  if (factory) IWICImagingFactory_Release(factory);
  return dst;
}

static int winTabsGetImageIndex(Ihandle* ih, const char* name)
{
  HIMAGELIST image_list;
  int count, i, ret;
  int img_w, img_h, dst_w, dst_h;
  Iarray* bmp_array;
  HBITMAP *bmp_array_data;
  Iarray* scaled_array;
  HBITMAP *scaled_array_data;
  HBITMAP bmp = iupImageGetImage(name, ih, 0, NULL);
  if (!bmp)
    return -1;

  bmp_array = (Iarray*)iupAttribGet(ih, "_IUPWIN_BMPARRAY");
  if (!bmp_array)
  {
    bmp_array = iupArrayCreate(50, sizeof(HBITMAP));
    iupAttribSet(ih, "_IUPWIN_BMPARRAY", (char*)bmp_array);
  }

  bmp_array_data = iupArrayGetData(bmp_array);

  iupdrvImageGetInfo(bmp, &img_w, &img_h, NULL);
  iupTabsScaleImageSize(ih, img_w, img_h, &dst_w, &dst_h);

  image_list = (HIMAGELIST)SendMessage(ih->handle, TCM_GETIMAGELIST, 0, 0);
  if (!image_list)
  {
    int cell_w, cell_h;
    iupTabsGetImageBoxSize(ih, &cell_w, &cell_h);
    if (cell_w <= 0 || cell_h <= 0)
    {
      cell_w = img_w;
      cell_h = img_h;
    }
    if (cell_w < dst_w) cell_w = dst_w;
    if (cell_h < dst_h) cell_h = dst_h;
    image_list = ImageList_Create(cell_w, cell_h, ILC_COLOR32, 0, 50);
    SendMessage(ih->handle, TCM_SETIMAGELIST, 0, (LPARAM)image_list);
  }

  count = ImageList_GetImageCount(image_list);
  for (i = 0; i < count; i++)
  {
    if (bmp_array_data[i] == bmp)
      return i;
  }

  bmp_array_data = iupArrayInc(bmp_array);
  bmp_array_data[i] = bmp;

  scaled_array = (Iarray*)iupAttribGet(ih, "_IUPWIN_SCALED_BMPARRAY");
  if (!scaled_array)
  {
    scaled_array = iupArrayCreate(50, sizeof(HBITMAP));
    iupAttribSet(ih, "_IUPWIN_SCALED_BMPARRAY", (char*)scaled_array);
  }
  scaled_array_data = iupArrayInc(scaled_array);

  if (dst_w != img_w || dst_h != img_h)
  {
    HBITMAP scaled = winTabsCreateScaledBitmap(bmp, img_w, img_h, dst_w, dst_h);
    scaled_array_data[i] = scaled;
    ret = ImageList_Add(image_list, scaled, NULL);
  }
  else
  {
    scaled_array_data[i] = NULL;
    ret = ImageList_Add(image_list, bmp, NULL);
  }
  return ret;
}

static void winTabGetPageWindowRect(Ihandle* ih, RECT *rect)
{
  GetClientRect(ih->handle, rect);

  if (ih->data->type == ITABS_LEFT || ih->data->type == ITABS_RIGHT)
  {
    /* For vertical tabs, measure the actual tab strip width */
    int tab_count = (int)SendMessage(ih->handle, TCM_GETITEMCOUNT, 0, 0);
    int tab_width = 0;

    if (tab_count > 0)
    {
      RECT tab_rect;
      /* Get the rectangle of the first tab to measure tab strip width */
      SendMessage(ih->handle, TCM_GETITEMRECT, 0, (LPARAM)&tab_rect);

      if (ih->data->type == ITABS_LEFT)
      {
        /* Tabs on left: content starts after tab strip width */
        tab_width = tab_rect.right - tab_rect.left;
        rect->left = tab_width + 4;  /* 4px border */
        rect->top = 4;
        rect->right -= 4;
        rect->bottom -= 4;
      }
      else  /* ITABS_RIGHT */
      {
        /* Content ends before tab strip */
        tab_width = tab_rect.right - tab_rect.left;
        rect->left = 4;
        rect->top = 4;
        rect->right -= (tab_width + 4);
        rect->bottom -= 4;
      }

      if (rect->left >= rect->right || rect->top >= rect->bottom)
      {
        rect->left = 0;
        rect->top = 0;
        rect->right = 0;
        rect->bottom = 0;
      }
    }
  }
  else
  {
    /* Horizontal tabs */
    SendMessage(ih->handle, TCM_ADJUSTRECT, FALSE, (LPARAM)rect);
  }
}

static void winTabSetPageWindowPos(HWND tab_container, RECT *rect)
{
  if (rect->right <= 0 || rect->bottom <= 0 ||
      rect->left >= rect->right || rect->top >= rect->bottom)
    return;

  /* Only position visible pages. Hidden pages should not be positioned at all. */
  LONG style = GetWindowLong(tab_container, GWL_STYLE);
  BOOL has_visible_style = (style & WS_VISIBLE) != 0;

  if (!has_visible_style)
  {
    return;
  }

  /* Position the IupTabsPage window */
  SetWindowPos(tab_container, NULL,
                rect->left, rect->top,
                rect->right - rect->left, rect->bottom - rect->top,
                SWP_NOACTIVATE | SWP_NOZORDER);
}

static void winTabsPlacePageWindows(Ihandle* ih, RECT* rect)
{
  Ihandle* child;

  for (child = ih->firstchild; child; child = child->brother)
  {
    HWND tab_container = (HWND)iupAttribGet(child, "_IUPTAB_PAGE");
    winTabSetPageWindowPos(tab_container, rect);
  }
}

static int winTabsUsingXPStyles(Ihandle* ih)
{
  return iupwin_comctl32ver6 && ih->data->type == ITABS_TOP && !(ih->data->show_close);
}

static void winTabsDrawPageBackground(Ihandle* ih, HDC hDC, RECT* rect)
{
  unsigned char r=0, g=0, b=0;
  char* color = iupAttribGetInheritNativeParent(ih, "BGCOLOR");
  if (!color) color = iupAttribGetInheritNativeParent(ih, "BACKGROUND");
  if (!color) color = iupAttribGet(ih, "BACKGROUND");
  if (!color) color = IupGetGlobal("DLGBGCOLOR");
  iupStrToRGB(color, &r, &g, &b);
  SetDCBrushColor(hDC, RGB(r,g,b));
  FillRect(hDC, rect, (HBRUSH)GetStockObject(DC_BRUSH));
}

static LRESULT CALLBACK winTabsPageWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
  switch (msg)
  {
  case WM_PARENTNOTIFY:
    break;
  case WM_ERASEBKGND:
    {
      Ihandle* ih = iupwinHandleGet(hWnd);
      if (iupObjectCheck(ih)) /* should always be ok */
      {
        RECT rect;
        HDC hDC = (HDC)wp;
        GetClientRect(ih->handle, &rect);
        winTabsDrawPageBackground(ih, hDC, &rect);

        /* return non zero value */
        return 1;
      }
      break;
    }
  case WM_COMMAND:
  case WM_CTLCOLORSCROLLBAR:
  case WM_CTLCOLORBTN:
  case WM_CTLCOLOREDIT:
  case WM_CTLCOLORLISTBOX:
  case WM_CTLCOLORSTATIC:
  case WM_DRAWITEM:
  case WM_HSCROLL:
  case WM_NOTIFY:
  case WM_VSCROLL:
    {
      /* Dispatch directly; forwarding to the tab control corrupts the tab strip. */
      Ihandle* ih = iupwinHandleGet(hWnd);
      if (iupObjectCheck(ih))
      {
        LRESULT result = 0;
        if (iupwinBaseContainerMsgProc(ih, msg, wp, lp, &result))
          return result;
      }
      break;
    }
  }

  return DefWindowProc(hWnd, msg, wp, lp);
}

static HWND winTabsCreatePageWindow(Ihandle* ih)
{
  HWND hWnd;
  DWORD dwStyle = WS_CHILD|WS_CLIPSIBLINGS,
      dwExStyle = 0;

  iupwinGetNativeParentStyle(ih, &dwExStyle, &dwStyle);

  hWnd = iupwinCreateWindowEx(ih->handle, TEXT("IupTabsPage"), dwExStyle, dwStyle, 0, NULL);

  iupwinHandleAdd(ih, hWnd);

  return hWnd;
}

static void winTabsInsertItem(Ihandle* ih, Ihandle* child, int pos, HWND tab_container)
{
  TCITEM tie;
  char *tabtitle, *tabimage;
  int old_rowcount = 0, old_num_tabs, p;
  RECT rect;

  tabtitle = iupAttribGet(child, "TABTITLE");
  if (!tabtitle)
  {
    tabtitle = iupAttribGetId(ih, "TABTITLE", pos);
    if (tabtitle)
      iupAttribSetStr(child, "TABTITLE", tabtitle);
  }
  tabimage = iupAttribGet(child, "TABIMAGE");
  if (!tabimage)
  {
    tabimage = iupAttribGetId(ih, "TABIMAGE", pos);
    if (tabimage)
      iupAttribSetStr(child, "TABIMAGE", tabimage);
  }
  if (!tabtitle && !tabimage)
    tabtitle = "     ";

  old_num_tabs = (int)SendMessage(ih->handle, TCM_GETITEMCOUNT, 0, 0);

  if (ih->data->is_multiline)
    old_rowcount = (int)SendMessage(ih->handle, TCM_GETROWCOUNT, 0, 0);

  tie.mask = TCIF_PARAM;

  if (tabtitle)
  {
    tie.mask |= TCIF_TEXT;
    tie.pszText = iupwinStrToSystem(tabtitle);
    tie.cchTextMax = lstrlen(tie.pszText);

    iupwinSetMnemonicTitle(ih, pos, tabtitle);
  }

  if (tabimage)
  {
    tie.mask |= TCIF_IMAGE;
    tie.iImage = winTabsGetImageIndex(ih, tabimage);
  }

  p = winTabsGetInsertPos(ih, pos);

  /* create tabs and label them */
  tie.lParam = (LPARAM)tab_container;
  SendMessage(ih->handle, TCM_INSERTITEM, p, (LPARAM)&tie);

  if (old_num_tabs == 0)
  {
    /* First page - show it */
    ShowWindow(tab_container, SW_SHOWNOACTIVATE);
    SendMessage(ih->handle, TCM_SETCURSEL, 0, 0);
  }
  else
  {
    /* All other pages - hide */
    ShowWindow(tab_container, SW_HIDE);
  }

  winTabGetPageWindowRect(ih, &rect);
  winTabSetPageWindowPos(tab_container, &rect);

  if (ih->data->is_multiline)
  {
    int rowcount = (int)SendMessage(ih->handle, TCM_GETROWCOUNT, 0, 0);
    if (rowcount != old_rowcount)
    {
      winTabsPlacePageWindows(ih, &rect);

      IupRefreshChildren(ih);
    }

    iupdrvRedrawNow(ih);
  }
}

static void winTabsDeleteItem(Ihandle* ih, int p, HWND tab_container)
{
  int old_rowcount = 0;
  if (ih->data->is_multiline)
    old_rowcount = (int)SendMessage(ih->handle, TCM_GETROWCOUNT, 0, 0);

  /* Make sure tab container is hidden */
  ShowWindow(tab_container, SW_HIDE);

  SendMessage(ih->handle, TCM_DELETEITEM, p, 0);

  if (ih->data->is_multiline)
  {
    int rowcount = (int)SendMessage(ih->handle, TCM_GETROWCOUNT, 0, 0);
    if (rowcount != old_rowcount)
    {
      RECT rect;
      winTabGetPageWindowRect(ih, &rect);
      winTabsPlacePageWindows(ih, &rect);

      IupRefreshChildren(ih);
    }

    iupdrvRedrawNow(ih);
  }
}

/* ------------------------------------------------------------------------- */
/* winTabs - Sets and Gets Attrib                                           */
/* ------------------------------------------------------------------------- */

static int winTabsSetTabPaddingAttrib(Ihandle* ih, const char* value)
{
  iupStrToIntInt(value, &ih->data->horiz_padding, &ih->data->vert_padding, 'x');

  if (ih->handle)
  {
    int extra_h = (ih->data->show_close)? ITABS_CLOSE_SIZE: 0;
    SendMessage(ih->handle, TCM_SETPADDING, 0, MAKELPARAM(ih->data->horiz_padding + extra_h, ih->data->vert_padding));
    return 0;
  }
  else
    return 1; /* store until not mapped, when mapped will be set again */
}

static int winTabsSetMultilineAttrib(Ihandle* ih, const char* value)
{
  if (ih->handle) /* allow to set only before mapping */
    return 0;

  if (iupStrBoolean(value))
    ih->data->is_multiline = 1;
  else
  {
    if (ih->data->type == ITABS_BOTTOM || ih->data->type == ITABS_TOP)
      ih->data->is_multiline = 0;
    else
      ih->data->is_multiline = 1;   /* always true if left/right */
  }

  return 0;
}

static char* winTabsGetMultilineAttrib(Ihandle* ih)
{
  return iupStrReturnBoolean (ih->data->is_multiline);
}

static int winTabsSetTabTypeAttrib(Ihandle* ih, const char* value)
{
  if (ih->handle) /* allow to set only before mapping */
    return 0;

  if(iupStrEqualNoCase(value, "BOTTOM"))
  {
    ih->data->type = ITABS_BOTTOM;
    ih->data->orientation = ITABS_HORIZONTAL;  /* TABTYPE controls TABORIENTATION in Windows */
  }
  else if(iupStrEqualNoCase(value, "LEFT"))
  {
    ih->data->type = ITABS_LEFT;
    ih->data->orientation = ITABS_VERTICAL;  /* TABTYPE controls TABORIENTATION in Windows */
    ih->data->is_multiline = 1; /* VERTICAL works only with MULTILINE */
  }
  else if(iupStrEqualNoCase(value, "RIGHT"))
  {
    ih->data->type = ITABS_RIGHT;
    ih->data->orientation = ITABS_VERTICAL;  /* TABTYPE controls TABORIENTATION in Windows */
    ih->data->is_multiline = 1; /* VERTICAL works only with MULTILINE */
  }
  else /* "TOP" */
  {
    ih->data->type = ITABS_TOP;
    ih->data->orientation = ITABS_HORIZONTAL;  /* TABTYPE controls TABORIENTATION in Windows */
  }

  return 0;
}

static int winTabsSetTabTitleAttrib(Ihandle* ih, int pos, const char* value)
{
  Ihandle* child = IupGetChild(ih, pos);
  if (child)
    iupAttribSetStr(child, "TABTITLE", value);

  if (value)
  {
    int p = winTabsPosFixToWin(ih, pos);
    if (p >= 0)
    {
      TCITEM tie;

      tie.mask = TCIF_TEXT;
      tie.pszText = iupwinStrToSystem(value);
      tie.cchTextMax = lstrlen(tie.pszText);

      iupwinSetMnemonicTitle(ih, pos, value);

      SendMessage(ih->handle, TCM_SETITEM, p, (LPARAM)&tie);
    }
  }

  return 0;
}

static int winTabsSetTabImageAttrib(Ihandle* ih, int pos, const char* value)
{
  int p;
  Ihandle* child = IupGetChild(ih, pos);
  if (child)
    iupAttribSetStr(child, "TABIMAGE", value);

  p = winTabsPosFixToWin(ih, pos);
  if (p >= 0)
  {
    TCITEM tie;

    tie.mask = TCIF_IMAGE;
    if (value)
      tie.iImage = winTabsGetImageIndex(ih, value);
    else
      tie.iImage = -1;

    SendMessage(ih->handle, TCM_SETITEM, p, (LPARAM)&tie);
  }

  return 1;
}

static int winTabsSetTabVisibleAttrib(Ihandle* ih, int pos, const char* value)
{
  Ihandle* child = IupGetChild(ih, pos);
  if (child)
  {
    int p = winTabsPosFixToWin(ih, pos);
    if (iupStrBoolean(value))
    {
      if (p < 0)  /* is invisible */
      {
        HWND tab_container = (HWND)iupAttribGet(child, "_IUPTAB_PAGE");

        winTabsSetVisibleArrayItem(ih, pos, 1);  /* to visible */

        winTabsInsertItem(ih, child, pos, tab_container);
      }
    }
    else
    {
      if (p >= 0)  /* is visible */
      {
        HWND tab_container = (HWND)iupAttribGet(child, "_IUPTAB_PAGE");

        iupTabsCheckCurrentTab(ih, pos, 0);
        winTabsSetVisibleArrayItem(ih, pos, 0);  /* to invisible */

        winTabsDeleteItem(ih, p, tab_container);
      }
    }
  }
  return 0;
}

IUP_SDK_API int iupdrvTabsIsTabVisible(Ihandle* child, int pos)
{
  Ihandle* ih = child->parent;
  return winTabsIsTabVisible(ih, pos);
}

static char* winTabsGetBgColorAttrib(Ihandle* ih)
{
  /* the most important use of this is to provide
     the correct background for images */
  if (iupwin_comctl32ver6)
  {
    COLORREF cr;
    if (iupwinDrawGetThemeTabsBgColor(ih->handle, &cr))
      return iupStrReturnStrf("%d %d %d", (int)GetRValue(cr), (int)GetGValue(cr), (int)GetBValue(cr));
  }

  return IupGetGlobal("DLGBGCOLOR");
}

static int winTabsSetBgColorAttrib(Ihandle *ih, const char *value)
{
  (void)value;
  iupdrvPostRedraw(ih);
  return 1;
}

static int winTabsSetAllowReorderAttrib(Ihandle* ih, const char* value)
{
  (void)value;
  return 1;
}

static void winTabsReorderTab(Ihandle* ih, int source_index, int target_index)
{
  TCITEM source_item;
  TCHAR text_buffer[256];
  int insert_index, current_sel;
  Ihandle* source_child;
  Ihandle* target_child;

  IFnii cb = (IFnii)IupGetCallback(ih, "REORDER_CB");
  if (cb && cb(ih, source_index, target_index) == IUP_IGNORE)
    return;

  int source_p = winTabsPosFixToWin(ih, source_index);
  int target_p = winTabsPosFixToWin(ih, target_index);

  if (source_p < 0 || target_p < 0)
    return;

  source_item.mask = TCIF_TEXT | TCIF_PARAM | TCIF_IMAGE;
  source_item.pszText = text_buffer;
  source_item.cchTextMax = 256;
  SendMessage(ih->handle, TCM_GETITEM, source_p, (LPARAM)&source_item);

  SendMessage(ih->handle, TCM_DELETEITEM, source_p, 0);

  insert_index = target_p;

  SendMessage(ih->handle, TCM_INSERTITEM, insert_index, (LPARAM)&source_item);

  source_child = IupGetChild(ih, source_index);

  if (source_child)
  {
    Ihandle* ref_child;

    if (source_index < target_index)
      ref_child = IupGetChild(ih, target_index + 1);
    else
      ref_child = IupGetChild(ih, target_index);

    iupAttribSet(ih, "_IUPTABS_REORDERING", "1");
    IupReparent(source_child, ih, ref_child);
    iupAttribSet(ih, "_IUPTABS_REORDERING", NULL);
  }

  current_sel = (int)SendMessage(ih->handle, TCM_GETCURSEL, 0, 0);

  /* Hide the previously active tab's container */
  if (current_sel >= 0 && current_sel < (int)SendMessage(ih->handle, TCM_GETITEMCOUNT, 0, 0))
  {
    int prev_active_pos = winTabsPosFixFromWin(ih, current_sel);
    HWND prev_tab_container = winTabsGetPageWindow(ih, prev_active_pos);
    if (prev_tab_container)
      ShowWindow(prev_tab_container, SW_HIDE);
  }

  /* Always activate the moved tab after reordering */
  SendMessage(ih->handle, TCM_SETCURSEL, insert_index, 0);

  /* Show the newly active (moved) tab's container */
  {
    HWND new_tab_container = winTabsGetPageWindow(ih, target_index);
    if (new_tab_container)
      ShowWindow(new_tab_container, SW_SHOW);
  }

  IupRefresh(ih);
}

static int winTabsIsInsideCloseButton(Ihandle* ih, int p)
{
  RECT rect;
  POINT pt;
  int border = 4, start, end;

  GetCursorPos(&pt);
  ScreenToClient(ih->handle, &pt);

  /* Get tab rectangle and define start/end positions of the image */
  SendMessage(ih->handle, TCM_GETITEMRECT, (WPARAM)p, (LPARAM)&rect);

  if(ih->data->type == ITABS_BOTTOM || ih->data->type == ITABS_TOP)
  {
    end = rect.right - border;
    start = end - ITABS_CLOSE_SIZE;
    if (pt.x >= start && pt.x <= end)
    {
      start = ((rect.bottom - rect.top) - ITABS_CLOSE_SIZE) / 2 + rect.top;
      end = start + ITABS_CLOSE_SIZE;
      if (pt.y >= start && pt.y <= end)
        return 1;
    }
  }
  else if(ih->data->type == ITABS_LEFT)
  {
    start = rect.top + border;
    end = start + ITABS_CLOSE_SIZE;
    if (pt.y >= start && pt.y <= end)
    {
      start = ((rect.right - rect.left) - ITABS_CLOSE_SIZE) / 2 + rect.left;
      end = start + ITABS_CLOSE_SIZE;
      if (pt.x >= start && pt.x <= end)
        return 1;
    }
  }
  else  /* ITABS_RIGHT */
  {
    end = rect.bottom - border;
    start = end - ITABS_CLOSE_SIZE;
    if (pt.y >= start && pt.y <= end)
    {
      start = ((rect.right - rect.left) - ITABS_CLOSE_SIZE) / 2 + rect.left;
      end = start + ITABS_CLOSE_SIZE;
      if (pt.x >= start && pt.x <= end)
        return 1;
    }
  }

  return 0;
}

static int winTabsCtlColor(Ihandle* ih, HDC hdc, LRESULT *result)
{
  /* works only when NOT winTabsUsingXPStyles */
  COLORREF bgcolor;
  if (iupwinGetParentBgColor(ih, &bgcolor))
  {
    SetDCBrushColor(hdc, bgcolor);
    *result = (LRESULT)GetStockObject(DC_BRUSH);
    return 1;
  }
  return 0;
}

static int winTabsWmNotify(Ihandle* ih, NMHDR* msg_info, int *result)
{
  (void)result;

  if (msg_info->code == TCN_SELCHANGING)
  {
    IFnnn cb = (IFnnn)IupGetCallback(ih, "TABCHANGE_CB");
    int prev_pos = iupdrvTabsGetCurrentTab(ih);
    iupAttribSetInt(ih, "_IUPWINTABS_PREV_CHILD_POS", prev_pos);

    /* save the previous handle if callback exists */
    if (cb)
    {
      Ihandle* prev_child = IupGetChild(ih, prev_pos);
      iupAttribSet(ih, "_IUPWINTABS_PREV_CHILD", (char*)prev_child);
    }

    return 0;
  }

  if (msg_info->code == TCN_SELCHANGE)
  {
    IFnnn cb = (IFnnn)IupGetCallback(ih, "TABCHANGE_CB");
    int pos = iupdrvTabsGetCurrentTab(ih);
    int prev_pos = iupAttribGetInt(ih, "_IUPWINTABS_PREV_CHILD_POS");

    HWND tab_container = winTabsGetPageWindow(ih, pos);
    HWND prev_tab_container = winTabsGetPageWindow(ih, prev_pos);

    /* Call the callback first, before showing/hiding windows */
    if (cb)
    {
      Ihandle* child = IupGetChild(ih, pos);
      Ihandle* prev_child = (Ihandle*)iupAttribGet(ih, "_IUPWINTABS_PREV_CHILD");
      iupAttribSet(ih, "_IUPWINTABS_PREV_CHILD", NULL);

      /* avoid duplicate calls when a Tab is inside another Tab. */
      if (prev_child)
        cb(ih, child, prev_child);
    }
    else
    {
      IFnii cb2 = (IFnii)IupGetCallback(ih, "TABCHANGEPOS_CB");
      if (cb2)
        cb2(ih, pos, prev_pos);
    }

    /* Show the new page and position it, hide all others. */
    RECT rect;
    winTabGetPageWindowRect(ih, &rect);

    Ihandle* child;
    int i = 0;
    for (child = ih->firstchild; child; child = child->brother, i++)
    {
      HWND page = (HWND)iupAttribGet(child, "_IUPTAB_PAGE");
      if (page) {
        if (i == pos) {
          /* Position the page FIRST */
          SetWindowPos(page, NULL,
                        rect.left, rect.top,
                        rect.right - rect.left, rect.bottom - rect.top,
                        SWP_NOACTIVATE | SWP_NOZORDER);

          /* Then show it */
          ShowWindow(page, SW_SHOWNA);

          /* Bring to front */
          SetWindowPos(page, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
        } else {
          ShowWindow(page, SW_HIDE);
        }
      }
    }

    return 0;
  }

  if (msg_info->code == NM_RCLICK)
  {
    IFni cb = (IFni)IupGetCallback(ih, "RIGHTCLICK_CB");
    if (cb)
    {
      TCHITTESTINFO ht;
      int p;

      GetCursorPos(&ht.pt);
      ScreenToClient(ih->handle, &ht.pt);

      p = (int)SendMessage(ih->handle, TCM_HITTEST, 0, (LPARAM)&ht);
      if (p >= 0)
      {
        int pos = winTabsPosFixFromWin(ih, p);
        cb(ih, pos);
      }
    }

    return 0;
  }

  return 0; /* result not used */
}

static int winTabsMsgProc(Ihandle* ih, UINT msg, WPARAM wp, LPARAM lp, LRESULT *result)
{
  switch (msg)
  {
  case WM_SIZE:
  {
    RECT rect;
    WNDPROC oldProc = (WNDPROC)IupGetCallback(ih, "_IUPWIN_OLDWNDPROC_CB");
    CallWindowProc(oldProc, ih->handle, msg, wp, lp);

    winTabGetPageWindowRect(ih, &rect);
    winTabsPlacePageWindows(ih, &rect);

    *result = 0;
    return 1;
  }
  case WM_PAINT:
    if (iupAttribGetInt(ih, "_IUPTABS_DRAGGING"))
    {
      WNDPROC oldProc = (WNDPROC)IupGetCallback(ih, "_IUPWIN_OLDWNDPROC_CB");
      CallWindowProc(oldProc, ih->handle, msg, wp, lp);

      {
        HDC hdc = GetDC(ih->handle);
        int target = iupAttribGetInt(ih, "_IUPTABS_DRAG_TARGET");
        int target_p = winTabsPosFixToWin(ih, target);
        int source = iupAttribGetInt(ih, "_IUPTABS_DRAG_SOURCE");
        int source_p = winTabsPosFixToWin(ih, source);

        if (target_p >= 0 && source_p >= 0 && target != source)
        {
          RECT rc;
          HPEN pen, old_pen;
          int x;

          SendMessage(ih->handle, TCM_GETITEMRECT, target_p, (LPARAM)&rc);

          pen = CreatePen(PS_SOLID, 2, RGB(0, 120, 215));
          old_pen = (HPEN)SelectObject(hdc, pen);

          if (source < target)
            x = rc.right;
          else
            x = rc.left;

          MoveToEx(hdc, x, rc.top, NULL);
          LineTo(hdc, x, rc.bottom);

          SelectObject(hdc, old_pen);
          DeleteObject(pen);
        }

        ReleaseDC(ih->handle, hdc);
      }

      *result = 0;
      return 1;
    }
    break;
  case WM_MOUSELEAVE:
    if (ih->data->show_close)
    {
      int high_p = iupAttribGetInt(ih, "_IUPTABS_CLOSEHIGH");
      if (high_p != -1)
      {
        iupAttribSetInt(ih, "_IUPTABS_CLOSEHIGH", -1);
        iupdrvRedrawNow(ih);
      }
    }
    break;
  case WM_MOUSEMOVE:
    {
      TCHITTESTINFO ht;
      int p, high_p, press_p;

      ht.pt.x = GET_X_LPARAM(lp);
      ht.pt.y = GET_Y_LPARAM(lp);
      p = (int)SendMessage(ih->handle, TCM_HITTEST, 0, (LPARAM)&ht);

      if (ih->data->show_close)
      {
        high_p = iupAttribGetInt(ih, "_IUPTABS_CLOSEHIGH");
        if (winTabsIsInsideCloseButton(ih, p))
        {
          if (high_p != p)
          {
            iupwinTrackMouseLeave(ih);
            iupAttribSetInt(ih, "_IUPTABS_CLOSEHIGH", p);
            iupdrvRedrawNow(ih);
          }
        }
        else
        {
          if (high_p != -1)
          {
            iupAttribSetInt(ih, "_IUPTABS_CLOSEHIGH", -1);
            iupdrvRedrawNow(ih);
          }
        }

        press_p = iupAttribGetInt(ih, "_IUPTABS_CLOSEPRESS");
        if (press_p != -1 && !winTabsIsInsideCloseButton(ih, press_p))
        {
          iupAttribSetInt(ih, "_IUPTABS_CLOSEPRESS", -1);
          iupdrvRedrawNow(ih);
        }
      }

      if (iupAttribGetInt(ih, "_IUPTABS_DRAGGING"))
      {
        if (p >= 0)
        {
          int pos = winTabsPosFixFromWin(ih, p);
          int old_target = iupAttribGetInt(ih, "_IUPTABS_DRAG_TARGET");
          if (pos != old_target)
          {
            iupAttribSetInt(ih, "_IUPTABS_DRAG_TARGET", pos);
            InvalidateRect(ih->handle, NULL, FALSE);
          }
        }
      }
    }
    break;
  case WM_LBUTTONDOWN:
    {
      TCHITTESTINFO ht;
      int p;

      ht.pt.x = GET_X_LPARAM(lp);
      ht.pt.y = GET_Y_LPARAM(lp);
      p = (int)SendMessage(ih->handle, TCM_HITTEST, 0, (LPARAM)&ht);

      if (ih->data->show_close)
      {
        if (p >= 0 && winTabsIsInsideCloseButton(ih, p))
        {
          iupAttribSetInt(ih, "_IUPTABS_CLOSEPRESS", p);
          iupdrvRedrawNow(ih);

          *result = 0;
          return 1;
        }
        else
          iupAttribSetInt(ih, "_IUPTABS_CLOSEPRESS", -1);
      }

      if (iupAttribGetBoolean(ih, "ALLOWREORDER") && p >= 0 && !winTabsIsInsideCloseButton(ih, p))
      {
        int pos = winTabsPosFixFromWin(ih, p);
        iupAttribSetInt(ih, "_IUPTABS_DRAGGING", 1);
        iupAttribSetInt(ih, "_IUPTABS_DRAG_SOURCE", pos);
        iupAttribSetInt(ih, "_IUPTABS_DRAG_TARGET", pos);
        iupAttribSetInt(ih, "_IUPTABS_DRAG_START_X", ht.pt.x);
        iupAttribSetInt(ih, "_IUPTABS_DRAG_START_Y", ht.pt.y);
        SetCapture(ih->handle);
      }
    }
    break;
  case WM_LBUTTONUP:
    {
      int is_dragging = iupAttribGetInt(ih, "_IUPTABS_DRAGGING");

      if (is_dragging)
      {
        int source = iupAttribGetInt(ih, "_IUPTABS_DRAG_SOURCE");
        int target = iupAttribGetInt(ih, "_IUPTABS_DRAG_TARGET");

        ReleaseCapture();
        iupAttribSet(ih, "_IUPTABS_DRAGGING", NULL);

        if (source != target)
          winTabsReorderTab(ih, source, target);

        InvalidateRect(ih->handle, NULL, TRUE);

        *result = 0;
        return 1;
      }

      if (ih->data->show_close)
      {
        int press_p = iupAttribGetInt(ih, "_IUPTABS_CLOSEPRESS");
        if (press_p != -1)
        {
          if (winTabsIsInsideCloseButton(ih, press_p))
          {
            int pos = winTabsPosFixFromWin(ih, press_p);
            Ihandle *child = IupGetChild(ih, pos);
            HWND tab_container = (HWND)iupAttribGet(child, "_IUPTAB_PAGE");

            iupAttribSetInt(ih, "_IUPTABS_CLOSEPRESS", -1);

            if (tab_container)
            {
              int ret = IUP_DEFAULT;
              IFni cb = (IFni)IupGetCallback(ih, "TABCLOSE_CB");
              if (cb)
                ret = cb(ih, pos);

              if (ret == IUP_CONTINUE)
              {
                IupDestroy(child);
                IupRefreshChildren(ih);
              }
              else if (ret == IUP_DEFAULT)
              {
                iupTabsCheckCurrentTab(ih, pos, 0);
                winTabsSetVisibleArrayItem(ih, pos, 0);
                winTabsDeleteItem(ih, press_p, tab_container);
              }
              else if (ret == IUP_IGNORE)
              {
                *result = 0;
                return 1;
              }
            }
          }

          iupdrvRedrawNow(ih);
        }
      }
    }
    break;
  case WM_NOTIFY:
  {
    NMHDR* msg_info = (NMHDR*)lp;
    if (msg_info && msg_info->code == TTN_GETDISPINFO)
    {
      NMTTDISPINFO* tips_info = (NMTTDISPINFO*)msg_info;
      TCHITTESTINFO ht;
      int pos;

      GetCursorPos(&ht.pt);
      ScreenToClient(ih->handle, &ht.pt);
      pos = (int)SendMessage(ih->handle, TCM_HITTEST, 0, (LPARAM)&ht);
      if (pos >= 0)
      {
        int iup_pos = winTabsPosFixFromWin(ih, pos);
        char* tabtip = iupAttribGetId(ih, "TABTIP", iup_pos);
        if (tabtip)
        {
          tips_info->lpszText = iupwinStrToSystem(tabtip);
          tips_info->hinst = NULL;
        }
      }

      *result = 0;
      return 1;
    }
    break;
  }
  }

  return iupwinBaseContainerMsgProc(ih, msg, wp, lp, result);
}

static void winTabsDrawRotateText(HDC hDC, char* text, int x, int y, HFONT hFont, COLORREF fgcolor, int align)
{
  COLORREF oldcolor;
  HFONT hOldFont;

  LOGFONT lf;
  GetObject(hFont, sizeof(LOGFONT), &lf);  /* get current font */
  if (align)   /* bottom to top */
  {
    lf.lfEscapement = 900;  /* rotate 90 degrees CCW */
    lf.lfOrientation = 900;
  }
  else         /* top to bottom */
  {
    lf.lfEscapement = -900;  /* rotate 90 degrees CW */
    lf.lfOrientation = -900;
  }
  hFont = CreateFontIndirect(&lf);  /* set font in order to rotate text */

  hOldFont = (HFONT)SelectObject(hDC, hFont);

  SetTextAlign(hDC, align ? TA_TOP | TA_LEFT : TA_BOTTOM | TA_LEFT);
  SetBkMode(hDC, TRANSPARENT);
  oldcolor = SetTextColor(hDC, fgcolor);

  {
    int len = (int)strlen(text);
    TCHAR* str = iupwinStrToSystemLen(text, &len);
    TextOut(hDC, x, y, str, len);
  }

  SelectObject(hDC, hOldFont);
  SetTextColor(hDC, oldcolor);
  SetBkMode(hDC, OPAQUE);

  DeleteObject(hFont);
}

static void winTabsDrawTab(Ihandle* ih, HDC hDC, int p, int width, int height, COLORREF fgcolor)
{
  HBITMAP hBitmapClose;
  HFONT hFont = (HFONT)iupwinGetHFontAttrib(ih);
  TCHAR title[256] = TEXT("");
  TCITEM tci;
  HIMAGELIST image_list = (HIMAGELIST)SendMessage(ih->handle, TCM_GETIMAGELIST, 0, 0);
  int imgW = 0, imgH = 0, txtW = 0, txtH = 0,
    bpp, style = 0, x = 0, y = 0, border = 4;
  char *str = NULL, *value = NULL;
  int high_p, press_p;

  tci.mask = TCIF_TEXT | TCIF_IMAGE;
  tci.pszText = title;
  tci.cchTextMax = 255;
  SendMessage(ih->handle, TCM_GETITEM, (WPARAM)p, (LPARAM)(&tci));

  if (title[0])
  {
    value = iupwinStrFromSystem(title);
    str = iupStrProcessMnemonic(value, NULL, 0);   /* remove & */
    iupdrvFontGetMultiLineStringSize(ih, str, &txtW, &txtH);
  }

  if (tci.iImage != -1)
  {
    IMAGEINFO pImgInfo;
    ImageList_GetImageInfo(image_list, tci.iImage, &pImgInfo);
    imgW = pImgInfo.rcImage.right - pImgInfo.rcImage.left;
    imgH = pImgInfo.rcImage.bottom - pImgInfo.rcImage.top;
  }

  /* Create the close button image */
  high_p = iupAttribGetInt(ih, "_IUPTABS_CLOSEHIGH");
  if (high_p == p)
    hBitmapClose = iupImageGetImage("IMGCLOSEHIGH", ih, 0, NULL);
  else
    hBitmapClose = iupImageGetImage("IMGCLOSE", ih, 0, NULL);
  if (!hBitmapClose)
  {
    if (str && str != value) free(str);
    return;
  }

  iupdrvImageGetInfo(hBitmapClose, NULL, NULL, &bpp);

  /* Draw image tab, title tab and close image */
  if (ih->data->type == ITABS_BOTTOM || ih->data->type == ITABS_TOP)
  {
    x = border;

    if (tci.iImage != -1)
    {
      y = (height - imgH) / 2;
      ImageList_Draw(image_list, tci.iImage, hDC, x, y, ILD_NORMAL);
      x += imgW + border;
    }

    if (title[0])
    {
      y = (height - txtH) / 2;
      iupwinDrawText(hDC, str, x, y, txtW, txtH, hFont, fgcolor, style);
    }

    x = width - border - ITABS_CLOSE_SIZE;
    y = (height - ITABS_CLOSE_SIZE) / 2;
  }
  else if(ih->data->type == ITABS_LEFT)
  {
    y = height;

    if (tci.iImage != -1)
    {
      x = (width - imgW) / 2;
      y -= border + imgH;
      ImageList_Draw(image_list, tci.iImage, hDC, x, y, ILD_NORMAL);  /* Tab image is already rotated into the list! */
    }

    if (title[0])
    {
      x = (width - txtH) / 2;  /* text is rotated switch W/H */
      y -= border;
      winTabsDrawRotateText(hDC, str, x, y, hFont, fgcolor, 1);
    }

    x = (width - ITABS_CLOSE_SIZE) / 2;
    y = border;
  }
  else  /* ITABS_RIGHT */
  {
    y = border;

    if (tci.iImage != -1)
    {
      x = (width - imgW) / 2;
      ImageList_Draw(image_list, tci.iImage, hDC, x, y, ILD_NORMAL);  /* Tab image is already rotated into the list! */
      y += imgH + border;
    }

    if (title[0])
    {
      x = (width - txtH) / 2;  /* text is rotated switch W/H */
      winTabsDrawRotateText(hDC, str, x, y, hFont, fgcolor, 0);
    }

    x = (width - ITABS_CLOSE_SIZE) / 2;
    y = height - border - ITABS_CLOSE_SIZE;
  }

  press_p = iupAttribGetInt(ih, "_IUPTABS_CLOSEPRESS");
  if (press_p == p)
  {
    x++;
    y++;
  }

  iupwinDrawBitmap(hDC, hBitmapClose, x, y, ITABS_CLOSE_SIZE, ITABS_CLOSE_SIZE, ITABS_CLOSE_SIZE, ITABS_CLOSE_SIZE, bpp);

  if (str && str != value) free(str);
}

static void winTabsDrawItem(Ihandle* ih, DRAWITEMSTRUCT *drawitem)
{
  HDC hDC;
  iupwinBitmapDC bmpDC;
  RECT rect;
  int x, y, width, height, p;
  COLORREF fgcolor;
  COLORREF bgcolor;

  /* called only when SHOWCLOSE=Yes */

  /* If there are no tab items, skip this message */
  if (drawitem->itemID == -1)
    return;

  p = drawitem->itemID;

  x = drawitem->rcItem.left;
  y = drawitem->rcItem.top;
  width = drawitem->rcItem.right - drawitem->rcItem.left;
  height = drawitem->rcItem.bottom - drawitem->rcItem.top;

  hDC = iupwinDrawCreateBitmapDC(&bmpDC, drawitem->hDC, x, y, width, height);

  if (iupwinGetParentBgColor(ih, &bgcolor))
  {
    SetDCBrushColor(hDC, bgcolor);
    SetRect(&rect, 0, 0, width, height);
    FillRect(hDC, &rect, (HBRUSH)GetStockObject(DC_BRUSH));
  }

  if (drawitem->itemState & ODS_DISABLED)
    fgcolor = GetSysColor(COLOR_GRAYTEXT);
  else
  {
    if (!iupwinGetColorRef(ih, "FGCOLOR", &fgcolor))
      fgcolor = GetSysColor(COLOR_WINDOWTEXT);
  }

  winTabsDrawTab(ih, hDC, p, width, height, fgcolor);

  /* If the item has the focus, draw the focus rectangle */
  if (drawitem->itemState & ODS_FOCUS)
    iupwinDrawFocusRect(hDC, 0, 0, width, height);

  iupwinDrawDestroyBitmapDC(&bmpDC);
}

static int winTabsSetShowCloseAttrib(Ihandle* ih, const char* value)
{
  int prev = ih->data->show_close;
  ih->data->show_close = iupStrBoolean(value);

  if (ih->handle && ih->data->show_close != prev)
  {
    DWORD dwStyle = (DWORD)GetWindowLong(ih->handle, GWL_STYLE);

    if (ih->data->show_close)
    {
      dwStyle |= TCS_OWNERDRAWFIXED;
      SetWindowLong(ih->handle, GWL_STYLE, dwStyle);

      SendMessage(ih->handle, TCM_SETPADDING, 0, MAKELPARAM(ih->data->horiz_padding + ITABS_CLOSE_SIZE, ih->data->vert_padding));

      iupAttribSetInt(ih, "_IUPTABS_CLOSEHIGH", -1);
      iupAttribSetInt(ih, "_IUPTABS_CLOSEPRESS", -1);

      IupSetCallback(ih, "_IUPWIN_DRAWITEM_CB", (Icallback)winTabsDrawItem);

      if (iupwin_comctl32ver6 && ih->data->type == ITABS_TOP)
        iupwinDrawRemoveTheme(ih->handle);
    }
    else
    {
      dwStyle &= ~TCS_OWNERDRAWFIXED;
      SetWindowLong(ih->handle, GWL_STYLE, dwStyle);

      SendMessage(ih->handle, TCM_SETPADDING, 0, MAKELPARAM(ih->data->horiz_padding, ih->data->vert_padding));

      iupAttribSet(ih, "_IUPTABS_CLOSEHIGH", NULL);
      iupAttribSet(ih, "_IUPTABS_CLOSEPRESS", NULL);

      IupSetCallback(ih, "_IUPWIN_DRAWITEM_CB", NULL);
    }

    InvalidateRect(ih->handle, NULL, TRUE);
  }

  return 1;
}

/* ------------------------------------------------------------------------- */
/* winTabs - Methods and Init Class                                          */
/* ------------------------------------------------------------------------- */

static void winTabsChildAddedMethod(Ihandle* ih, Ihandle* child)
{
  if (iupAttribGet(ih, "_IUPTABS_REORDERING"))
    return;

  /* make sure it has at least one name */
  if (!iupAttribGetHandleName(child))
    iupAttribSetHandleName(child);

  if (ih->handle)
  {
    int pos = IupGetChildPos(ih, child);

    HWND tab_container = winTabsCreatePageWindow(ih);

    iupAttribSet(child, "_IUPTAB_CONTAINER", (char*)tab_container);
    iupAttribSet(child, "_IUPTAB_PAGE", (char*)tab_container);

    winTabsInsertVisibleArrayItem(ih, pos);

    winTabsInsertItem(ih, child, pos, tab_container);
  }
}

static void winTabsChildRemovedMethod(Ihandle* ih, Ihandle* child, int pos)
{
  if (iupAttribGet(ih, "_IUPTABS_REORDERING"))
    return;

  if (ih->handle)
  {
    HWND tab_container = (HWND)iupAttribGet(child, "_IUPTAB_PAGE");
    if (tab_container)
    {
      int p = winTabsGetPageWindowPos(ih, tab_container);

      iupTabsCheckCurrentTab(ih, pos, 1);

      winTabsDeleteVisibleArrayItem(ih, pos);
      if (p != -1) winTabsDeleteItem(ih, p, tab_container);

      iupwinHandleRemove(tab_container);
      DestroyWindow(tab_container);
      iupAttribSet(child, "_IUPTAB_CONTAINER", NULL);
      iupAttribSet(child, "_IUPTAB_PAGE", NULL);
    }
  }
}

static int winTabsMapMethod(Ihandle* ih)
{
  DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS | TCS_HOTTRACK | WS_TABSTOP | TCS_TOOLTIPS,
      dwExStyle = 0;

  if (!ih->parent)
    return IUP_ERROR;

  if (ih->data->type == ITABS_BOTTOM)
    dwStyle |= TCS_BOTTOM;
  else if (ih->data->type == ITABS_RIGHT)
    dwStyle |= TCS_VERTICAL|TCS_RIGHT;
  else if (ih->data->type == ITABS_LEFT)
    dwStyle |= TCS_VERTICAL;

  if (ih->data->is_multiline)
    dwStyle |= TCS_MULTILINE;

  if (ih->data->show_close)
    dwStyle |= TCS_OWNERDRAWFIXED;

  iupwinGetNativeParentStyle(ih, &dwExStyle, &dwStyle);

  if (dwExStyle & WS_EX_COMPOSITED && !ih->data->is_multiline && iupwinIsVistaOrNew())
  {
    /* workaround for composite bug in Vista */
    ih->data->is_multiline = 1;
    dwStyle |= TCS_MULTILINE;
  }

  if (!iupwinCreateWindow(ih, WC_TABCONTROL, dwExStyle, dwStyle, NULL))
    return IUP_ERROR;

  /* replace the WinProc to handle other messages */
  IupSetCallback(ih, "_IUPWIN_CTRLMSGPROC_CB", (Icallback)winTabsMsgProc);

  /* Process WM_NOTIFY */
  IupSetCallback(ih, "_IUPWIN_NOTIFY_CB", (Icallback)winTabsWmNotify);

  /* Process background color */
  IupSetCallback(ih, "_IUPWIN_CTLCOLOR_CB", (Icallback)winTabsCtlColor);

  if (ih->data->show_close)
  {
    /* change tab width to draw a close image */
    SendMessage(ih->handle, TCM_SETPADDING, 0, MAKELPARAM(ITABS_CLOSE_SIZE, 0));

    iupAttribSetInt(ih, "_IUPTABS_CLOSEHIGH", -1);
    iupAttribSetInt(ih, "_IUPTABS_CLOSEPRESS", -1);

    /* owner draw tab image + tab title + close image */
    IupSetCallback(ih, "_IUPWIN_DRAWITEM_CB", (Icallback)winTabsDrawItem);
  }

  if (iupwin_comctl32ver6 && (ih->data->type != ITABS_TOP || ih->data->show_close))
  {
    /* XP Styles support only TABTYPE=TOP,
       show_close ownerdraw will work only with classic style */
    iupwinDrawRemoveTheme(ih->handle);
  }

  /* Change children background */
  if (winTabsUsingXPStyles(ih))
  {
    char* color = iupAttribGetInheritNativeParent(ih, "BGCOLOR");
    if (!color)
      color = iupAttribGetInheritNativeParent(ih, "BACKGROUND");
    if (!color)
    {
      COLORREF cr;
      if (iupwinDrawGetThemeTabsBgColor(ih->handle, &cr))
        iupAttribSetStrf(ih, "BACKGROUND", "%d %d %d", (int)GetRValue(cr), (int)GetGValue(cr), (int)GetBValue(cr));
    }
  }

  /* Create pages and tabs */
  if (ih->firstchild)
  {
    Ihandle* child;
    Ihandle* current_child = (Ihandle*)iupAttribGet(ih, "_IUPTABS_VALUE_HANDLE");

    for (child = ih->firstchild; child; child = child->brother)
      winTabsChildAddedMethod(ih, child);

    if (current_child)
    {
      IupSetAttribute(ih, "VALUE_HANDLE", (char*)current_child);

      /* current value is now given by the native system */
      iupAttribSet(ih, "_IUPTABS_VALUE_HANDLE", NULL);
    }

    /* Hide all pages except the currently active one. */
    int current_tab = iupdrvTabsGetCurrentTab(ih);

    int pos = 0;
    for (child = ih->firstchild; child; child = child->brother, pos++)
    {
      HWND tab_container = (HWND)iupAttribGet(child, "_IUPTAB_PAGE");
      if (tab_container)
      {
        if (pos == current_tab) {
          ShowWindow(tab_container, SW_SHOWNA);
        } else {
          ShowWindow(tab_container, SW_HIDE);
        }
      }
    }
  }

  return IUP_NOERROR;
}

static void winTabsUnMapMethod(Ihandle* ih)
{
  Iarray* iarray;

  HIMAGELIST image_list = (HIMAGELIST)SendMessage(ih->handle, TCM_GETIMAGELIST, 0, 0);
  if (image_list)
    ImageList_Destroy(image_list);

  iarray = (Iarray*)iupAttribGet(ih, "_IUPWIN_BMPARRAY");
  if (iarray)
  {
    iupAttribSet(ih, "_IUPWIN_BMPARRAY", NULL);
    iupArrayDestroy(iarray);
  }

  iarray = (Iarray*)iupAttribGet(ih, "_IUPWIN_SCALED_BMPARRAY");
  if (iarray)
  {
    HBITMAP* data = (HBITMAP*)iupArrayGetData(iarray);
    int n = iupArrayCount(iarray);
    int k;
    for (k = 0; k < n; k++)
      if (data[k]) DeleteObject(data[k]);
    iupAttribSet(ih, "_IUPWIN_SCALED_BMPARRAY", NULL);
    iupArrayDestroy(iarray);
  }

  iarray = (Iarray*)iupAttribGet(ih, "_IUPWIN_VISIBLEARRAY");
  if (iarray)
  {
    iupAttribSet(ih, "_IUPWIN_VISIBLEARRAY", NULL);
    iupArrayDestroy(iarray);
  }

  iupdrvBaseUnMapMethod(ih);
}

static void winTabsRegisterClass(void)
{
  WNDCLASS wndclass;

  ZeroMemory(&wndclass, sizeof(WNDCLASS));

  wndclass.hInstance      = iupwin_hinstance;
  wndclass.lpszClassName  = TEXT("IupTabsPage");
  wndclass.lpfnWndProc    = (WNDPROC)winTabsPageWndProc;
  wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
  wndclass.style          = CS_PARENTDC;

  wndclass.hbrBackground  = NULL;  /* remove the background to optimize redraw */
  RegisterClass(&wndclass);
}

static void winTabsRelease(Iclass* ic)
{
  (void)ic;

  if (iupwinClassExist(TEXT("IupTabsPage")))
    UnregisterClass(TEXT("IupTabsPage"), iupwin_hinstance);
}

IUP_SDK_API void iupdrvTabsInitClass(Iclass* ic)
{
  if (!iupwinClassExist(TEXT("IupTabsPage")))
    winTabsRegisterClass();

  /* Driver Dependent Class functions */
  ic->Map = winTabsMapMethod;
  ic->UnMap = winTabsUnMapMethod;
  ic->ChildAdded     = winTabsChildAddedMethod;
  ic->ChildRemoved   = winTabsChildRemovedMethod;
  ic->Release = winTabsRelease;

  /* Driver Dependent Attribute functions */

  iupClassRegisterCallback(ic, "TABCLOSE_CB", "i");

  /* Visual */
  iupClassRegisterAttribute(ic, "BGCOLOR", winTabsGetBgColorAttrib, winTabsSetBgColorAttrib, IUPAF_SAMEASSYSTEM, "DLGBGCOLOR", IUPAF_NO_SAVE);

  /* Special */
  iupClassRegisterAttribute(ic, "FGCOLOR", NULL, NULL, IUPAF_SAMEASSYSTEM, "DLGFGCOLOR", IUPAF_DEFAULT);

  /* IupTabs only */
  iupClassRegisterAttribute(ic, "TABTYPE", iupTabsGetTabTypeAttrib, winTabsSetTabTypeAttrib, IUPAF_SAMEASSYSTEM, "TOP", IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "TABORIENTATION", iupTabsGetTabOrientationAttrib, NULL, IUPAF_SAMEASSYSTEM, "HORIZONTAL", IUPAF_READONLY|IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);  /* can not be set, depends on TABTYPE in Windows */
  iupClassRegisterAttribute(ic, "MULTILINE", winTabsGetMultilineAttrib, winTabsSetMultilineAttrib, NULL, NULL, IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
  iupClassRegisterAttributeId(ic, "TABTITLE", iupTabsGetTitleAttrib, winTabsSetTabTitleAttrib, IUPAF_NO_DEFAULTVALUE | IUPAF_NO_INHERIT);
  iupClassRegisterAttributeId(ic, "TABTIP", NULL, NULL, IUPAF_NO_INHERIT);
  iupClassRegisterAttributeId(ic, "TABIMAGE", NULL, winTabsSetTabImageAttrib, IUPAF_IHANDLENAME|IUPAF_NO_DEFAULTVALUE|IUPAF_NO_INHERIT);
  iupClassRegisterAttributeId(ic, "TABVISIBLE", iupTabsGetTabVisibleAttrib, winTabsSetTabVisibleAttrib, IUPAF_NO_DEFAULTVALUE | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "TABPADDING", iupTabsGetTabPaddingAttrib, winTabsSetTabPaddingAttrib, IUPAF_SAMEASSYSTEM, "0x0", IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "ALLOWREORDER", NULL, winTabsSetAllowReorderAttrib, IUPAF_SAMEASSYSTEM, "NO", IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "SHOWCLOSE", NULL, winTabsSetShowCloseAttrib, NULL, NULL, IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);

  /* necessary because transparent background does not work when not using visual styles */
  if (!iupwin_comctl32ver6)  /* Used by iupdrvImageCreateImage */
    iupClassRegisterAttribute(ic, "FLAT_ALPHA", NULL, NULL, IUPAF_SAMEASSYSTEM, "YES", IUPAF_NO_INHERIT);

  /* Default node images */
  if (!IupGetHandle("IMGCLOSE"))
    winTabsInitializeCloseImage();

  iupClassRegisterAttribute(ic, "CONTROLID", NULL, NULL, NULL, NULL, IUPAF_NO_INHERIT);
}
