/************************************************************************
    MeOS - Orienteering Software
    Copyright (C) 2009-2017 Melin Software HB

    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 3 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, see <http://www.gnu.org/licenses/>.

    Melin Software HB - software@melin.nu - www.melin.nu
    Eksoppsvägen 16, SE-75646 UPPSALA, Sweden

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

#include "stdafx.h"
#include "Table.h"
#include "gdioutput.h"
#include "meos_util.h"
#include <exception>
#include <algorithm>
#include <set>
#include "oEvent.h"
#include "localizer.h"
#include <cassert>
#include "gdiconstants.h"
#include "meosexception.h"
#include "recorder.h"
#include "oDataContainer.h"

extern HINSTANCE hInst;
const char *tId="_TABLE_SEL";

const Table *TableSortIndex::table = 0;

int Table::uniqueId = 1;

Table::Table(oEvent *oe_, int rowH,
             const wstring &name, const string &iName)
{
  id = uniqueId++;
  commandLock = false;
  oe=oe_;
  tableName=name;
  internalName = iName;
  nTitles=0;
  PrevSort=-1;
  baseRowHeight=rowH;
  rowHeight = 0;
  highRow=-1;
  highCol=-1;
  colSelected=-1;

  editRow=-1;
  editCol=-1;
  drawFilterLabel=false;
  currentSortColumn=-1;
  hEdit=0;

  hdcCompatible=0;
  hbmStored=0;

  hdcCompatibleCell = 0;
  hbmStoredCell = 0;
  partialCell = false;

  startSelect = false;
  ownerCounter = 0;
  clearCellSelection(0);
  tableProp = -1;
  dataPointer = -1;

  clearOnHide = true;
  doAutoSelectColumns = true;

  generator = 0;
  generatorPtr = 0;
}

Table::~Table(void)
{
  assert(ownerCounter == 0);
  if (hEdit)
    DestroyWindow(hEdit);
}


void Table::releaseOwnership() {
  ownerCounter--;
  if (ownerCounter == 0)
    delete this;
}

void Table::clearCellSelection(gdioutput *gdi) {
  upperRow = -1;
  lowerRow = -1;
  upperCol = -1;
  lowerCol = -1;
  if (gdi) {
    HDC hDC = GetDC(gdi->getHWNDTarget());
    clearSelectionBitmap(gdi, hDC);
    ReleaseDC(gdi->getHWNDTarget(), hDC);
  }
}

int Table::addColumn(const string &Title, int width, bool isnum, bool formatRight) {
  ColInfo ri;
  wcscpy_s(ri.name, lang.tl(Title).c_str());
  ri.baseWidth = width;
  ri.width = 0;
  ri.padWidthZeroSort = 0;
  ri.isnumeric = isnum;
  ri.formatRight = formatRight;
  Titles.push_back(ri);
  columns.push_back(nTitles);
  nTitles++;
  return Titles.size()-1;
}

int Table::addColumnPaddedSort(const string &title, int width, int padding, bool formatRight) {
  ColInfo ri;
  wcscpy_s(ri.name, lang.tl(title).c_str());
  ri.baseWidth = width;
  ri.width = 0;
  ri.padWidthZeroSort = padding;
  ri.isnumeric = false;
  ri.formatRight = formatRight;
  Titles.push_back(ri);
  columns.push_back(nTitles);
  nTitles++;
  return Titles.size()-1;
}


void Table::moveColumn(int src, int target)
{
  if (src==target)
    return;

  vector<int>::iterator it_s=find(columns.begin(), columns.end(), src);
  vector<int>::iterator it_t=find(columns.begin(), columns.end(), target);

  if (it_s!=columns.end()) {

    if (it_s<it_t) {
      columns.erase(it_s);
      it_t=find(columns.begin(), columns.end(), target);

      if (it_t!=columns.end())
        ++it_t;

      columns.insert(it_t, src);
    }
    else {
      columns.erase(it_s);
      it_t=find(columns.begin(), columns.end(), target);
      columns.insert(it_t, src);
    }
  }
}

void Table::reserve(size_t siz) {
  Data.reserve(siz+3);
  sortIndex.reserve(siz+3);
  idToRow.resize(siz+3);
}

TableRow *Table::getRowById(int rowId) {
  int ix;
  if (idToRow.lookup(rowId, ix)) {
    return &Data[ix];
  }
  return 0;
}

void Table::addRow(int rowId, oBase *object)
{
  int ix;
  if (rowId>0 && idToRow.lookup(rowId, ix)) {
    dataPointer = ix;
    return;
  }

  TableRow tr(nTitles, object);
  tr.height=rowHeight;
  tr.id = rowId;
  TableSortIndex tsi;

  if (Data.empty()) {
    sortIndex.clear();
    tsi.index=0;
    sortIndex.push_back(tsi);
    tsi.index=1;
    sortIndex.push_back(tsi);

    tsi.index=2;
    Data.resize(2, tr);

    for (unsigned i=0;i<nTitles;i++) {
      Data[0].cells[i].contents=Titles[i].name;
      Data[1].cells[i].contents = L"...";

      Data[0].cells[i].canEdit=false;
      Data[0].cells[i].type=cellEdit;
      Data[0].cells[i].owner=0;

      Data[1].cells[i].canEdit=false;
      Data[1].cells[i].type=cellEdit;
      Data[1].cells[i].owner=0;
    }
  }
  else {
    //tsi.index=sortIndex.size();
    tsi.index = Data.size();
  }
  if (rowId>0)
    idToRow[rowId]=Data.size();

  dataPointer = Data.size();
  sortIndex.push_back(tsi);
  Data.push_back(tr);
}

void Table::set(int column, oBase &owner, int id, const wstring &data, bool canEdit, CellType type)
{
  if (dataPointer >= Data.size() || dataPointer<2)
    throw std::exception("Internal table error: wrong data pointer");

  TableRow &row=Data[dataPointer];
  TableCell &cell=row.cells[column];
  cell.contents=data;
  cell.owner=&owner;
  cell.id=id;
  cell.canEdit=canEdit;
  cell.type=type;
}

void Table::filter(int col, const wstring &filt, bool forceFilter)
{
  const wstring &oldFilter=Titles[col].filter;
  vector<TableSortIndex> baseIndex;

  if (filt==oldFilter && (!forceFilter || filt.empty()))
    return;
  else if (wcsncmp(oldFilter.c_str(), filt.c_str(), oldFilter.length())==0) {
    //Filter more...
    baseIndex.resize(2);
    baseIndex[0]=sortIndex[0];
    baseIndex[1]=sortIndex[1];
    swap(baseIndex, sortIndex);
    Titles[col].filter=filt;
  }
  else {
    //Filter less -> refilter all!
    Titles[col].filter=filt;
    sortIndex.resize(Data.size());
    for (size_t k=0; k<Data.size();k++) {
      sortIndex[k].index = k;
    }

    for (unsigned k=0;k<nTitles;k++) {
      filter(k, Titles[k].filter, true);
    }

    PrevSort = -1;
    return;
  }

  wchar_t filt_lc[1024];
  wcscpy_s(filt_lc, filt.c_str());
  CharLowerBuff(filt_lc, filt.length());

  sortIndex.resize(2);
  for (size_t k=2;k<baseIndex.size();k++) {
    if (filterMatchString(Data[baseIndex[k].index].cells[col].contents, filt_lc))
      sortIndex.push_back(baseIndex[k]);
  }
}

bool Table::compareRow(int indexA, int indexB) const {
  const TableRow &a = Data[indexA];
  const TableRow &b = Data[indexB];
  if (a.intKey != b.intKey && (a.intKey != 0xFEFEFEFE && b.intKey != 0xFEFEFEFE))
    return a.intKey < b.intKey;
  else
    return CompareString( LOCALE_USER_DEFAULT, 0, Data[indexA].key.c_str(), Data[indexA].key.length(),
                                                  Data[indexB].key.c_str(), Data[indexB].key.length()) == CSTR_LESS_THAN;
    //return Data[indexA].key < Data[indexB].key;
    //return Data[indexA].key < Data[indexB].key;
}

void Table::sort(int col)
{
  bool reverse = col < 0;
  if (col < 0)
    col = -(10+col);

  if (sortIndex.size()<2)
    return;
  currentSortColumn=col;
  if (PrevSort!=col && PrevSort!=-(10+col)) {
    if (Titles[col].isnumeric) {
      bool hasDeci = false;
      for(size_t k=2; k<sortIndex.size(); k++){
        Data[sortIndex[k].index].key.clear();
        const wchar_t *str = Data[sortIndex[k].index].cells[col].contents.c_str();

        int i = 0;
        while (str[i] != 0 && str[i] != ':' && str[i] != ',' && str[i] != '.')
          i++;

        if (str[i]) {
          hasDeci = true;
          break;
        }

        i = 0;
        while (str[i] != 0 && (str[i] < '0' || str[i] > '9'))
          i++;

        int key = _wtoi(str + i);
        Data[sortIndex[k].index].intKey = key;
        if (key == 0)
          Data[sortIndex[k].index].key = Data[sortIndex[k].index].cells[col].contents; 
      }

      if (hasDeci) { // Times etc.
       for(size_t k=2; k<sortIndex.size(); k++){
          Data[sortIndex[k].index].key.clear();
          const wchar_t *str = Data[sortIndex[k].index].cells[col].contents.c_str();

          int i = 0;
          while (str[i] != 0 && (str[i] < '0' || str[i] > '9'))
            i++;

          int key = 0;

          while (str[i] >= '0' && str[i] <= '9') {
            key = key * 10 + (str[i] - '0');
            i++;
          }

          if (str[i] == ':' || str[i]==',' || str[i] == '.' || (str[i] == '-' && key != 0)) {
            bool valid = true;
            for (int j = 1; j <= 4; j++) {
              if (valid && str[i+j] >= '0' && str[i+j] <= '9')
                key = key * 10 + (str[i+j] - '0');
              else {
                key *= 10;
                valid = false;
              }
            }
          }
          else {
            key *= 10000;
          }

          Data[sortIndex[k].index].intKey = key;
          if (key == 0)
            Data[sortIndex[k].index].key = Data[sortIndex[k].index].cells[col].contents; 
        }
      }
    }
    else {
      if (Titles[col].padWidthZeroSort) {
        for (size_t k=2; k<sortIndex.size(); k++) {
          wstring &key = Data[sortIndex[k].index].key;
          const wstring &contents = Data[sortIndex[k].index].cells[col].contents;
          if (contents.length() < unsigned(Titles[col].padWidthZeroSort)) {
            key.resize(Titles[col].padWidthZeroSort+1);
            int cl = Titles[col].padWidthZeroSort-contents.length();
            for (int i = 0; i < cl; i++)
              key[i] = '0';
            for (int i = cl; i < Titles[col].padWidthZeroSort; i++)
              key[i] = contents[i-cl];
          }
          else
            key = contents;

          const wchar_t *strBuff = (const wchar_t *)key.c_str();
          CharUpperBuff(LPWSTR(strBuff), key.size());

          int &intKey = Data[sortIndex[k].index].intKey;
          intKey = unsigned(strBuff[0])<<16;
          if (key.length() > 1) {
            intKey |= unsigned(strBuff[1]);
          }
        }
      }
      else {
        for (size_t k=2; k<sortIndex.size(); k++) {
          wstring &key = Data[sortIndex[k].index].key;
          key = Data[sortIndex[k].index].cells[col].contents;
          const wchar_t *strBuff = (const wchar_t *)key.c_str();
          CharUpperBuff(LPWSTR(strBuff), key.size());

          int &intKey = Data[sortIndex[k].index].intKey;
          
          if ( ((strBuff[0]|strBuff[1]) & ~127) == 0) {
            intKey = unsigned(strBuff[0])<<16;
            if (key.length() > 1) {
              intKey |= unsigned(strBuff[1])<<8;
              //intKey |= unsigned(strBuff[2]);
            }
          }
          else {
            intKey = 0xFEFEFEFE;
          }
        }
      }
    }
    assert(TableSortIndex::table == 0);
    TableSortIndex::table = this;
    //DWORD sStart = GetTickCount();
    std::stable_sort(sortIndex.begin()+2, sortIndex.end());
    //DWORD sEnd = GetTickCount();
    //string st = itos(sEnd-sStart);
    TableSortIndex::table = 0;
    PrevSort=col;

    if (reverse)
      std::reverse(sortIndex.begin()+2, sortIndex.end());
  }
  else {
    std::reverse(sortIndex.begin()+2, sortIndex.end());

    if (PrevSort==col)
      PrevSort=-(10+col);
    else
      PrevSort=col;
  }
}

int TablesCB(gdioutput *gdi, int type, void *data)
{
  if (type!=GUI_LINK || gdi->Tables.empty())
    return 0;

  TableInfo &tai=gdi->Tables.front();
  Table *t=tai.table;

  TextInfo *ti=(TextInfo *)data;


  if (ti->id.substr(0,4)=="sort"){
    int col=atoi(ti->id.substr(4).c_str());
    t->sort(col);
  }

  gdi->refresh();
  //gdi->Restore();
  //t->Render(*gdi);
//*/
  return 0;
}

void Table::getDimension(gdioutput &gdi, int &dx, int &dy, bool filteredResult) const
{
  rowHeight = gdi.scaleLength(baseRowHeight);

  if (filteredResult)
    dy = rowHeight * (1+sortIndex.size());
  else
    dy = rowHeight * (1+max<size_t>(2,Data.size()));

  dx=1;
  for(size_t i=0;i<columns.size();i++) {
    Titles[columns[i]].width = gdi.scaleLength(Titles[columns[i]].baseWidth);
    dx += Titles[columns[i]].width + 1;
  }
}

void Table::redrawCell(gdioutput &gdi, HDC hDC, int c, int r)
{
  if (unsigned(r)<Data.size() && unsigned(c)<unsigned(nTitles)) {
    const TableRow &row=Data[r];
    const TableCell &cell=row.cells[c];
    RECT rc=cell.absPos;
    draw(gdi, hDC, table_xp+gdi.OffsetX, table_yp+gdi.OffsetY, rc);
  }
}
void Table::startMoveCell(HDC hDC, const TableCell &cell)
{
  RECT rc=cell.absPos;
  int cx=rc.right-rc.left+1;
  int cy=rc.bottom-rc.top;

  hdcCompatible = CreateCompatibleDC(hDC);
  hbmStored = CreateCompatibleBitmap(hDC, cx, cy);

  // Select the bitmaps into the compatible DC.
  SelectObject(hdcCompatible, hbmStored);

  lastX=rc.left;
  lastY=rc.top;
  BitBlt(hdcCompatible, 0, 0, cx, cy, hDC, lastX, lastY, SRCCOPY);
}

void Table::restoreCell(HDC hDC, const TableCell &cell)
{
  const RECT &rc=cell.absPos;
  int cx=rc.right-rc.left+1;
  int cy=rc.bottom-rc.top;

  if (hdcCompatible) {
    //Restore bitmap
    BitBlt(hDC, lastX, lastY, cx, cy, hdcCompatible, 0, 0, SRCCOPY);
  }
}

void Table::moveCell(HDC hDC, gdioutput &gdi, const TableCell &cell, int dx, int dy)
{
  RECT rc=cell.absPos;
  rc.left+=dx; rc.right+=dx;
  rc.top+=dy;  rc.bottom+=dy;
  int cx=rc.right-rc.left+1;
  int cy=rc.bottom-rc.top;

  if (hdcCompatible) {
    lastX=rc.left;
    lastY=rc.top;
    //Take new
    BitBlt(hdcCompatible, 0, 0, cx, cy, hDC, lastX, lastY , SRCCOPY);
  }
  else {
    startMoveCell(hDC, cell);
    dx=0;
    dy=0;
  }
  highlightCell(hDC, gdi, cell, RGB(255, 0,0), dx, dy);
}

void Table::stopMoveCell(HDC hDC, const TableCell &cell, int dx, int dy)
{
  if (hdcCompatible) {
    restoreCell(hDC, cell);
/*    RECT rc=cell.absPos;
    rc.left+=dx; rc.right+=dx;
    rc.top+=dy;  rc.bottom+=dy;
    int cx=rc.right-rc.left+2;
    int cy=rc.bottom-rc.top+2;

    //Restore bitmap
    BitBlt(hDC, lastX, lastY, cx, cy, hdcCompatible, 0, 0, SRCCOPY);
*/
    DeleteDC(hdcCompatible);
    hdcCompatible=0;
  }

  if (hbmStored) {
    DeleteObject(hbmStored);
    hbmStored=0;
  }
}

bool Table::mouseMove(gdioutput &gdi, int x, int y)
{
  int row=getRow(y);
  int col=-1;

  if (row!=-1)
    col=getColumn(x);

  HWND hWnd=gdi.getHWNDTarget();

  if (colSelected!=-1) {
    TableCell &cell=Data[0].cells[colSelected];
    HDC hDC=GetDC(hWnd);

    restoreCell(hDC, cell);

    if (col!=highCol) {
      if (unsigned(highRow)<Data.size() && unsigned(highCol)<nTitles)
        redrawCell(gdi, hDC, highCol, highRow);

      DWORD c=RGB(240, 200, 140);
      if (unsigned(col)<nTitles)
        highlightCell(hDC, gdi, Data[0].cells[col], c, 0,0);
    }

    //highlightCell(hDC, cell, RGB(255,0,0), x-startX, y-startY);
    moveCell(hDC, gdi, cell,  x-startX, y-startY);
    ReleaseDC(hWnd, hDC);
    highRow=0;
    highCol=col;
    return false;
  }

  RECT rc;
  GetClientRect(hWnd, &rc);

  if (x<=rc.left || x>=rc.right || y<rc.top || y>rc.bottom)
    row=-1;

  bool ret = false;

  if (startSelect) {
    int c = getColumn(x, true);
    if (c != -1)
      upperCol = c;
    c = getRow(y, true);
    if (c != -1 && c>=0) {
      upperRow = max<int>(c, 2);
    }

    HDC hDC=GetDC(hWnd);
    if (unsigned(highRow)<Data.size() && unsigned(highCol)<Titles.size())
      redrawCell(gdi, hDC, highCol, highRow);
    highRow = -1;
    drawSelection(gdi, hDC, false);
    ReleaseDC(hWnd, hDC);
    scrollToCell(gdi, upperRow, upperCol);
    ret = true;
  }
  else if (row>=0 && col>=0) {
    POINT pt = {x, y};
    ClientToScreen(hWnd, &pt);
    HWND hUnder = WindowFromPoint(pt);

    if (hUnder == hWnd) {
      //int index=sortIndex[row].index;
      TableRow &trow=Data[row];
      TableCell &cell=trow.cells[col];

      if (highRow!=row || highCol!=col) {

        HDC hDC=GetDC(hWnd);

        if (unsigned(highRow)<Data.size() && unsigned(highCol)<Titles.size())
          redrawCell(gdi, hDC, highCol, highRow);

        if (row >= 2) {
          DWORD c;
          if (cell.canEdit)
            c=RGB(240, 240, 150);
          else
            c=RGB(240, 200, 140);

          highlightCell(hDC, gdi, cell, c, 0,0);
        }

        ReleaseDC(hWnd, hDC);
        SetCapture(hWnd);
        highCol=col;
        highRow=row;
      }
      ret = true;
    }
  }

  if (ret)
    return true;

  if (unsigned(highRow)<Data.size() && unsigned(highCol)<Titles.size()) {
    ReleaseCapture();
    HDC hDC=GetDC(hWnd);
    redrawCell(gdi, hDC, highCol, highRow);
    ReleaseDC(hWnd, hDC);
    highRow=-1;
  }
  return false;
}

bool Table::mouseLeftUp(gdioutput &gdi, int x, int y)
{
  if (colSelected!=-1) {

    if (hdcCompatible) {
      TableCell &cell=Data[0].cells[colSelected];
      HWND hWnd=gdi.getHWNDTarget();
      HDC hDC=GetDC(hWnd);
      stopMoveCell(hDC, cell, x-startX, y-startY);
      ReleaseDC(hWnd, hDC);
      //return true;
    }

    if (highRow==0 && colSelected==highCol) {
      colSelected=-1;
      gdi.setWaitCursor(true);
      sort(highCol);
      gdi.setWaitCursor(false);
      gdi.refresh();
      mouseMove(gdi, x, y);
      return true;
    }
    else {
      moveColumn(colSelected, highCol);
      InvalidateRect(gdi.getHWNDTarget(), 0, false);
      colSelected=-1;
      return true;
    }
  }
  else {
    upperCol = getColumn(x);
    upperRow = getRow(y);
    startSelect = false;
    ReleaseCapture();
  }
  colSelected=-1;
  return false;
}

int tblSelectionCB(gdioutput *gdi, int type, void *data)
{
  if (type == GUI_LISTBOX) {
    ListBoxInfo lbi = *static_cast<ListBoxInfo *>(data);
    Table &t = gdi->getTable();
    t.selection(*gdi, lbi.text, lbi.data);
  }
  return 0;
}

void Table::selection(gdioutput &gdi, const wstring &text, int data) {
 if (size_t(selectionRow) >= Data.size() || size_t(selectionCol) >= Titles.size())
   throw std::exception("Index out of bounds.");

 TableCell &cell = Data[selectionRow].cells[selectionCol];
 int id = Data[selectionRow].id;
 cell.owner->inputData(cell.id, text, data, cell.contents, false);
 reloadRow(id);
 RECT rc;
 getRowRect(selectionRow, rc);
 InvalidateRect(gdi.getHWNDTarget(), &rc, false);
}

#ifndef MEOSDB

bool Table::keyCommand(gdioutput &gdi, KeyCommandCode code) {
  if (commandLock)
    return false;
  commandLock = true;

  try {
    if (code == KC_COPY && hEdit == 0)
      exportClipboard(gdi);
    else if (code == KC_PASTE && hEdit == 0) {
      importClipboard(gdi);
    }else if (code == KC_DELETE && hEdit == 0) {
      deleteSelection(gdi);
    }
    else if (code == KC_REFRESH) {
      gdi.setWaitCursor(true);
      update();
      autoAdjust(gdi);
      gdi.refresh();
    }
    else if (code == KC_INSERT) {
      insertRow(gdi);
      gdi.refresh();
    }
    else if (code == KC_PRINT) {
      gdioutput gdiPrint("temp", gdi.getScale(), gdi.getCP());
      gdiPrint.clearPage(false);
      gdiPrint.print(getEvent(), this);
    }
  }
  catch (...) {
    commandLock = false;
    throw;
  }

  commandLock = false;
  return false;
}

#endif

bool Table::deleteSelection(gdioutput &gdi) {
  int r1, r2;
  getRowRange(r1, r2);
  if (r1 != -1 && r2 != -1 && r1<=r2) {
    if (!gdi.ask(L"Vill du radera X rader från tabellen?#" + itow(r2-r1+1)))
      return false;
    gdi.setWaitCursor(true);
    int failed = deleteRows(r1, r2);
    gdi.refresh();
    gdi.setWaitCursor(false);
    if (failed > 0)
      gdi.alert("X rader kunde inte raderas.#" + itos(failed));
  }
  return true;
}

void Table::hide(gdioutput &gdi) {
  try {
    destroyEditControl(gdi);
  }
  catch (meosException &ex) {
    gdi.alert(ex.wwhat());
  }
  catch (std::exception &ex) {
    gdi.alert(ex.what());
  }

  clearCellSelection(0);
  ReleaseCapture();
  if (clearOnHide) {
    Data.clear();
    sortIndex.clear();
    idToRow.clear();
    clear();
  }
}

void Table::clear() {
  Data.clear();
  sortIndex.clear();
  idToRow.clear();
}

bool Table::destroyEditControl(gdioutput &gdi) {
  colSelected=-1;

  if (hEdit) {
    try {
      if (!enter(gdi))
        return false;
    }
    catch (std::exception &) {
      if (hEdit) {
        DestroyWindow(hEdit);
        hEdit=0;
      }
      throw;
    }
    if (hEdit) {
      DestroyWindow(hEdit);
      hEdit=0;
    }
  }
  gdi.removeControl(tId);

  if (drawFilterLabel) {
    drawFilterLabel=false;
    gdi.refresh();
  }

  return true;
}

bool Table::mouseLeftDown(gdioutput &gdi, int x, int y) {
  partialCell = true;
  clearCellSelection(&gdi);

  if (!destroyEditControl(gdi))
    return false;

  if (highRow==0) {
    colSelected=highCol;
    startX=x;
    startY=y;
    //sort(highCol);
    //gdi.refresh();
    //mouseMove(gdi, x, y);
  }
  else if (highRow==1) {
    //filter(highCol, "lots");
    RECT rc=Data[1].cells[columns[0]].absPos;
    //rc.right=rc.left+tableWidth;
    editRow=highRow;
    editCol=highCol;

    hEdit=CreateWindowEx(0, L"EDIT", Titles[highCol].filter.c_str(),
        WS_TABSTOP|WS_VISIBLE | WS_CHILD | ES_AUTOHSCROLL|WS_BORDER,
        rc.left+105, rc.top, tableWidth-105, (rc.bottom-rc.top-1), gdi.getHWNDTarget(),
        0, hInst, 0);
    drawFilterLabel=true;
    SendMessage(hEdit, EM_SETSEL, 0, -1);
    SetFocus(hEdit);
    SendMessage(hEdit, WM_SETFONT, (WPARAM) gdi.getGUIFont(), 0);
    gdi.refresh();
  }
  else {
    SetFocus(gdi.getHWNDTarget());
    SetCapture(gdi.getHWNDTarget());
    lowerCol = getColumn(x);
    lowerRow = getRow(y);
    startSelect = true;
  }
  return false;
}

bool Table::mouseLeftDblClick(gdioutput &gdi, int x, int y)
{
  clearCellSelection(&gdi);

  if (!destroyEditControl(gdi))
    return false;

  if (unsigned(highRow)>=Data.size() || unsigned(highCol)>=Titles.size()) {
    return false;
  }


  if (highRow != 0 && highRow != 1) {
    if (editCell(gdi, highRow, highCol))
      return true;
  }
  return false;
}

bool Table::editCell(gdioutput &gdi, int row, int col) {
  TableCell &cell = Data[row].cells[col];

  if (cell.type == cellAction) {
    ReleaseCapture();
    gdi.makeEvent("CellAction", internalName, cell.id, cell.owner ? cell.owner->getId() : 0, false);
    return true;
  }

  if (!cell.canEdit) {
    MessageBeep(-1);
    return true;
  }
  ReleaseCapture();

  editRow = row;
  editCol = col;

  RECT &rc=cell.absPos;

  if (cell.type == cellSelection || cell.type == cellCombo) {
    selectionRow = row;
    selectionCol = col;

    vector< pair<wstring, size_t> > out;
    size_t selected = 0;
    cell.owner->fillInput(cell.id, out, selected);

    int width = 40;
    for (size_t k = 0; k<out.size(); k++)
      width = max<int>(width, 8*out[k].first.length());

    if (cell.type == cellSelection) {
      gdi.addSelection(rc.left+gdi.OffsetX, rc.top+gdi.OffsetY, tId,
              max<int>(int((rc.right-rc.left+1)/gdi.scale), width), (rc.bottom-rc.top)*10,
              tblSelectionCB).setExtra(id);
    }
    else {
      gdi.addCombo(rc.left+gdi.OffsetX, rc.top+gdi.OffsetY, tId,
              max<int>(int((rc.right-rc.left+1)/gdi.scale), width), (rc.bottom-rc.top)*10,
              tblSelectionCB).setExtra(id);
    }
    gdi.addItem(tId, out);
    gdi.selectItemByData(tId, selected);
    return true;
  }
  else if (cell.type==cellEdit) {
    hEdit=CreateWindowEx(0, L"EDIT", cell.contents.c_str(),
      WS_TABSTOP|WS_VISIBLE | WS_CHILD | ES_AUTOHSCROLL|WS_BORDER,
      rc.left, rc.top, rc.right-rc.left+1, (rc.bottom-rc.top), gdi.getHWNDTarget(),
      0, hInst, 0);
    SendMessage(hEdit, EM_SETSEL, 0, -1);
    SetFocus(hEdit);
    SendMessage(hEdit, WM_SETFONT, (WPARAM) gdi.getGUIFont(), 0);
    return true;
  }

  return false;
}

RGBQUAD RGBA(BYTE r, BYTE g, BYTE b, BYTE a) {
  RGBQUAD q = {b,g,r,a};
  return q;
}

RGBQUAD RGBA(DWORD c) {
  RGBQUAD q = {GetBValue(c),GetGValue(c),GetRValue(c),0};
  return q;
}

RGBQUAD transform(RGBQUAD src, double scale) {
  src.rgbRed = BYTE(min(src.rgbRed * scale, 255.0));
  src.rgbGreen = BYTE(min(src.rgbGreen * scale, 255.0));
  src.rgbBlue = BYTE(min(src.rgbBlue * scale, 255.0));
  return src;
}

void gradRect(HDC hDC, int left, int top, int right, int bottom,
              bool horizontal, RGBQUAD c1, RGBQUAD c2) {

  TRIVERTEX vert[2];
  vert [0] .x      = left;
  vert [0] .y      = top;
  vert [0] .Red    = 0xff00 & (DWORD(c1.rgbRed)<<8);
  vert [0] .Green  = 0xff00 & (DWORD(c1.rgbGreen)<<8);
  vert [0] .Blue   = 0xff00 & (DWORD(c1.rgbBlue)<<8);
  vert [0] .Alpha  = 0xff00 & (DWORD(c1.rgbReserved)<<8);

  vert [1] .x      = right;
  vert [1] .y      = bottom;
  vert [1] .Red    = 0xff00 & (DWORD(c2.rgbRed)<<8);
  vert [1] .Green  = 0xff00 & (DWORD(c2.rgbGreen)<<8);
  vert [1] .Blue   = 0xff00 & (DWORD(c2.rgbBlue)<<8);
  vert [1] .Alpha  = 0xff00 & (DWORD(c2.rgbReserved)<<8);

  GRADIENT_RECT gr[1];
  gr[0].UpperLeft=0;
  gr[0].LowerRight=1;

  if (horizontal)
    GradientFill(hDC,vert, 2, gr, 1,GRADIENT_FILL_RECT_H);
  else
    GradientFill(hDC,vert, 2, gr, 1,GRADIENT_FILL_RECT_V);
}

void Table::initEmpty() {
  if (Data.empty()) {
    addRow(0,0);
    Data.resize(2, TableRow(nTitles,0));
    sortIndex.resize(2);
  }
}

void drawSymbol(gdioutput &gdi, HDC hDC, int height,
                const TextInfo &ti, const wstring &symbol, bool highLight) {
  int cx = ti.xp - gdi.GetOffsetX() + ti.xlimit/2;
  int cy = ti.yp - gdi.GetOffsetY() + height/2 - 2;
  int h = int(height * 0.4);
  int w = h/3;
  h-=2;

  RGBQUAD a,b;
  if (highLight) {
    a = RGBA(32, 114, 14, 0);
    b = RGBA(64, 211, 44, 0);
  }
  else {
    a = RGBA(32, 114, 114, 0);
    b = RGBA(84, 231, 64, 0);
  }
  gradRect(hDC, cx-w, cy-h, cx+w, cy+h, false, a, b);
  gradRect(hDC, cx-h, cy-w, cx+h, cy+w, false, a, b);

  POINT pt;
  MoveToEx(hDC, cx-w, cy-h, &pt);
  SelectObject(hDC, GetStockObject(DC_PEN));
  SetDCPenColor(hDC, RGB(14, 80, 7));
  LineTo(hDC, cx+w, cy-h);
  LineTo(hDC, cx+w, cy-w);
  LineTo(hDC, cx+h, cy-w);
  LineTo(hDC, cx+h, cy+w);
  LineTo(hDC, cx+w, cy+w);
  LineTo(hDC, cx+w, cy+h);
  LineTo(hDC, cx-w, cy+h);
  LineTo(hDC, cx-w, cy+w);
  LineTo(hDC, cx-h, cy+w);
  LineTo(hDC, cx-h, cy-w);
  LineTo(hDC, cx-w, cy-w);
  LineTo(hDC, cx-w, cy-h);
}


void Table::highlightCell(HDC hDC, gdioutput &gdi, const TableCell &cell, DWORD color, int dx, int dy)
{
  SelectObject(hDC, GetStockObject(DC_BRUSH));
  SelectObject(hDC, GetStockObject(NULL_PEN));

  SetDCBrushColor(hDC, color);

  RECT rc=cell.absPos;
  rc.left+=dx;
  rc.right+=dx;
  rc.top+=dy;
  rc.bottom+=dy;

  Rectangle(hDC, rc.left+1, rc.top,
                 rc.right+2, rc.bottom);

  TextInfo ti;

  if (cell.type == cellAction && cell.contents[0] == '@') {
    ti.xp = rc.left + gdi.OffsetX;
    ti.yp = rc.top + gdi.OffsetY;
    ti.xlimit = rc.right - rc.left;

    drawSymbol(gdi, hDC, rowHeight, ti, cell.contents, true);
    //SetDCPenColor(hDC, RGB(190,190,190));
  }
  else {
    gdi.formatString(ti, hDC);
    SetBkMode(hDC, TRANSPARENT);
    rc.left+=4;
    rc.top+=2;
    DrawText(hDC, cell.contents.c_str(), -1, &rc, DT_LEFT|DT_NOPREFIX);
  }
}

void Table::draw(gdioutput &gdi, HDC hDC, int dx, int dy, const RECT &screen)
{
  gdi.resetLast();
  initEmpty();
  updateDimension(gdi);

  table_xp=dx-gdi.OffsetX;
  table_yp=dy-gdi.OffsetY;

  if (currentSortColumn==-1)
    sort(0);

  int wi, he;
  getDimension(gdi, wi, he, true);

  tableWidth=wi;
  tableHeight=he;

  //Find first and last row
  int yreltop=(screen.top-(dy-gdi.OffsetY)-rowHeight+1)/rowHeight;
  int yrelbottom=(screen.bottom-(dy-gdi.OffsetY)-1)/rowHeight;

  int firstRow=max(yreltop,0);
  int lastRow=min(yrelbottom, int(sortIndex.size()));

  int firstCol=0;
  int lastCol=columns.size();

  xpos.resize(columns.size()+1);

  xpos[0]=dx-gdi.OffsetX;
  for (size_t i=1;i<=columns.size();i++)
    xpos[i]=xpos[i-1]+Titles[columns[i-1]].width+1;

  //Find first and last visible column
  while(firstCol<int(columns.size()) && xpos[firstCol+1]<=screen.left)
    firstCol++;

  while(lastCol>0 && xpos[lastCol-1]>screen.right)
    lastCol--;

  SelectObject(hDC, GetStockObject(DC_BRUSH));
  SelectObject(hDC, GetStockObject(DC_PEN));

  SetDCPenColor(hDC, RGB(190,190,190));
  SetDCBrushColor(hDC, RGB(60,25,150));

  //Title
  TextInfo ti;
  gdi.formatString(ti, hDC);
  HLS hls;
  hls.RGBtoHLS(GetSysColor(COLOR_ACTIVECAPTION));
  hls.lighten(2);
  hls.saturation = min<WORD>(90,hls.saturation);
  DWORD lightColor = hls.HLStoRGB();

  if (firstRow==0 && lastRow>=0) {
    //Rectangle(hDC, dx-gdi.OffsetX, dy-gdi.OffsetY,
    //          dx+tableWidth-gdi.OffsetX, dy+rowHeight-gdi.OffsetY);
    RGBQUAD c1 = RGBA(GetSysColor(COLOR_ACTIVECAPTION));
    gradRect(hDC, dx-gdi.OffsetX, dy-gdi.OffsetY,
            dx+tableWidth-gdi.OffsetX, dy+rowHeight-gdi.OffsetY, false,
            c1, transform(c1, 0.7));

    RECT rc;
    rc.left=dx+5-gdi.OffsetX;
    rc.right=rc.left+gdi.scaleLength(150);
    rc.top=dy+3-gdi.OffsetY;
    rc.bottom=rc.top+rowHeight;
    ti.format = 1;
    gdi.formatString(ti, hDC);
    SetTextColor(hDC, RGB(255,255,255));
    DrawText(hDC, lang.tl(tableName).c_str(), -1, &rc, DT_LEFT|DT_NOPREFIX);
    DrawText(hDC, lang.tl(tableName).c_str(), -1, &rc, DT_CALCRECT|DT_NOPREFIX);
    ti.format = 0;
    gdi.formatString(ti, hDC);
    SetTextColor(hDC, RGB(255,255,255));
    wchar_t bf[256];
    wstring info = lang.tl(wstring(L"sortering: X, antal rader: Y#") + Titles[currentSortColumn].name + L"#" + itow(sortIndex.size()-2));
    //sprintf_s(bf, .c_str(),
    //          Titles[currentSortColumn].name, int(sortIndex.size())-2);
    rc.left=rc.right+30;
    rc.right=dx + tableWidth - gdi.OffsetX - 10;
    DrawText(hDC, info.c_str(), info.length(), &rc, DT_LEFT|DT_NOPREFIX);

    SetTextColor(hDC, RGB(0,0,0));

    if (drawFilterLabel) {
      SetDCBrushColor(hDC, RGB(100,200,100));

      Rectangle(hDC, dx-gdi.OffsetX, dy+2*rowHeight-gdi.OffsetY,
              dx+tableWidth-gdi.OffsetX, dy+3*rowHeight-gdi.OffsetY);

      rc.left=dx+5-gdi.OffsetX;
      rc.right=rc.left+100;
      rc.top=dy+3+2*rowHeight-gdi.OffsetY;
      rc.bottom=rc.top+rowHeight;
      wchar_t tbf[2];
      tbf[0]=Titles[editCol].name[0];
      tbf[1]=0;
      CharLower(tbf);
      wstring filter = lang.tl("Urval %c%s: ");
      swprintf_s(bf, filter.c_str(), tbf[0],
              Titles[editCol].name+1, int(sortIndex.size())-2);

      DrawText(hDC, bf, -1, &rc, DT_RIGHT|DT_NOPREFIX);
    }
  }

  int yp;
  yp=dy+rowHeight;

  if (firstRow<=2 && lastRow>=1) {
    wstring filterText = lang.tl("Urval...");

    for (int k=firstCol;k<lastCol;k++) {
      int xp=xpos[k];
      int index=columns[k];
      RECT rc;
      rc.left=xp;
      rc.right=rc.left+Titles[index].width;
      rc.top=yp+1-gdi.OffsetY;
      rc.bottom=rc.top+rowHeight;
      SetDCBrushColor(hDC, RGB(200,200,200));
      Rectangle(hDC, rc.left, rc.top-1, rc.right+2, rc.bottom);

      if (index == PrevSort || PrevSort == -(10+index) ) {
        SetDCBrushColor(hDC, RGB(100,250,100));
        POINT pt[3];
        int r = gdi.scaleLength(4);
        int s = rc.bottom - rc.top - r;
        int px = (rc.right + rc.left + s)/2;
        int py = rc.top + r/2;

        pt[0].x = px - s;
        pt[1].x = px - s/2;
        pt[2].x = px;
        if (index == PrevSort) {
          pt[0].y = py;
          pt[1].y = py + s;
          pt[2].y = py;
        }
        else {
          pt[0].y = py + s;
          pt[1].y = py;
          pt[2].y = py + s;
        }

        Polygon(hDC, pt, 3);
      }

      Data[0].cells[index].absPos=rc;
      RECT tp = rc;
      tp.top += gdi.scaleLength(1);
      DrawText(hDC, Titles[index].name, -1, &tp, DT_CENTER|DT_NOPREFIX);
      Titles[index].title=rc;

      if (!drawFilterLabel) {
        rc.top+=rowHeight;
        rc.bottom+=rowHeight;
        Titles[index].condition=rc;
        Data[1].cells[index].absPos=rc;
        if (Titles[index].filter.empty()) {
          SetDCBrushColor(hDC, lightColor);
          Rectangle(hDC, rc.left, rc.top-1, rc.right+2, rc.bottom);
          rc.left+=3;
          DrawText(hDC, filterText.c_str(), -1, &rc, DT_LEFT|DT_NOPREFIX);
        }
        else {
          SetDCBrushColor(hDC, RGB(230,100,100));
          Rectangle(hDC, rc.left, rc.top-1, rc.right+2, rc.bottom);
          rc.left+=3;
          DrawText(hDC, Titles[index].filter.c_str(), -1, &rc, DT_LEFT|DT_NOPREFIX);
        }
      }
    }
  }

  SelectObject(hDC, GetStockObject(DC_BRUSH));

  SetDCPenColor(hDC, RGB(190,190, 190));

  RECT desktop, target;
  GetClientRect(GetDesktopWindow(), &desktop);
  GetClientRect(gdi.getHWNDTarget(), &target);
  int marginPixel = (desktop.bottom - desktop.top)  - (target.bottom - target.top);

  int margin = max(5, (marginPixel + rowHeight) / rowHeight);

  const int rStart = max(2, firstRow - margin);
  const int rEnd = min<int>(lastRow + margin, sortIndex.size());
  for (int k1 = rStart; k1 < rEnd; k1++){
    int yp = dy + rowHeight*(k1+1);
    TableRow &tr = Data[sortIndex[k1].index];
    for(size_t k=0;k<columns.size();k++){
      int xp = xpos[k];
      int i = columns[k];
      tr.cells[i].absPos.left = xp;
      tr.cells[i].absPos.right = xp+Titles[i].width;
      tr.cells[i].absPos.top = yp-gdi.OffsetY;
      tr.cells[i].absPos.bottom = yp-gdi.OffsetY+rowHeight;
    }
  }

  for (size_t k1=max(2, firstRow); int(k1)<lastRow; k1++){
    int yp=dy+rowHeight*(k1+1);
    TableRow &tr=Data[sortIndex[k1].index];
    int xp=xpos[0];

    if (k1&1)
      SetDCBrushColor(hDC, RGB(230,230, 240));
    else
      SetDCBrushColor(hDC, RGB(230,230, 250));

    SelectObject(hDC, GetStockObject(NULL_PEN));
    Rectangle(hDC, max(xpos[firstCol], int(screen.left)), yp-gdi.OffsetY,
              min(xpos[lastCol]+1, int(screen.right+2)), yp+rowHeight-gdi.OffsetY);
    SelectObject(hDC, GetStockObject(DC_PEN));
    const int cy=yp+rowHeight-gdi.OffsetY-1;
    MoveToEx(hDC, xp, cy, 0);
    LineTo(hDC, xp+wi, cy);

    for(int k=firstCol;k<lastCol;k++) {
      xp=xpos[k];
      int i=columns[k];
      if (!tr.cells[i].contents.empty()) {
        ti.xp = xp + 4 + gdi.OffsetX;
        ti.yp = yp + 2;
        ti.format = 0;
        ti.xlimit = Titles[i].width - 4;

        if (tr.cells[i].type == cellAction && tr.cells[i].contents[0] == '@') {
          drawSymbol(gdi, hDC, rowHeight, ti, tr.cells[i].contents, false);
          SetDCPenColor(hDC, RGB(190,190,190));
        }
        else {
          if (Titles[i].formatRight) {
            ti.xlimit -= 4;
            ti.format = textRight;
          }
          gdi.RenderString(ti, tr.cells[i].contents, hDC);
        }
      }
    }
  }

  const int miy=dy+rowHeight-gdi.OffsetY;
  const int may=dy+rowHeight*(sortIndex.size()+1)-gdi.OffsetY;

  for (size_t i=firstCol;i<=columns.size();i++) {
    int xp=xpos[i];
    MoveToEx(hDC, xp, miy, 0);
    LineTo(hDC, xp, may);
  }


  SelectObject(hDC, GetStockObject(NULL_BRUSH));
  SelectObject(hDC, GetStockObject(DC_PEN));
  SetDCPenColor(hDC, RGB(64,64,64));
  Rectangle(hDC, dx-gdi.OffsetX, dy-gdi.OffsetY,
            dx+tableWidth-gdi.OffsetX, dy+tableHeight-gdi.OffsetY);
  SetDCPenColor(hDC, RGB(200,200,200));
  Rectangle(hDC, dx-gdi.OffsetX-1, dy-gdi.OffsetY-1,
            dx+tableWidth-gdi.OffsetX+1, dy+tableHeight-gdi.OffsetY+1);
  SetDCPenColor(hDC, RGB(64,64,64));
  Rectangle(hDC, dx-gdi.OffsetX-2, dy-gdi.OffsetY-2,
            dx+tableWidth-gdi.OffsetX+2, dy+tableHeight-gdi.OffsetY+2);
  drawSelection(gdi, hDC, true);
}


TableCell &Table::getCell(int row, int col) const {
  if (size_t(row) >= sortIndex.size())
    throw std::exception("Index out of range");
  const TableRow &tr = Data[sortIndex[row].index];

  if ( size_t(col) >= columns.size())
    throw std::exception("Index out of range");

  col = columns[col];
  return *((TableCell *)&tr.cells[col]);
}

void Table::clearSelectionBitmap(gdioutput *gdi, HDC hDC)
{
  if (gdi)
    restoreSelection(*gdi, hDC);

  if (hdcCompatibleCell) {
    DeleteDC(hdcCompatibleCell);
    hdcCompatibleCell = 0;
  }

  if (hbmStoredCell) {
    DeleteObject(hbmStoredCell);
    hbmStoredCell = 0;
  }
}

void Table::restoreSelection(gdioutput &gdi, HDC hDC) {
  if (partialCell) {
    RECT rc = lastCell;
    rc.left -= gdi.OffsetX;
    rc.right -= gdi.OffsetX;
    rc.top -= gdi.OffsetY;
    rc.bottom -= gdi.OffsetY;
    InvalidateRect(gdi.getHWNDTarget(), &rc, false);
    partialCell = false;
  }
  else if (hdcCompatibleCell) {
    //Restore bitmap
    int cx = lastCell.right - lastCell.left + 1;
    int cy = lastCell.bottom - lastCell.top + 1;
    int x = lastCell.left - 1 - gdi.OffsetX;
    int y = lastCell.top - 1 - gdi.OffsetY;
    BitBlt(hDC, x, y, cx, cy, hdcCompatibleCell, 0, 0, SRCCOPY);
  }
}

void Table::drawSelection(gdioutput &gdi, HDC hDC, bool forceDraw) {
  bool modified = false;
  if (lowerColOld != lowerCol || upperCol != upperColOld ||
      lowerRow != lowerRowOld || upperRow != upperRowOld) {
    modified = true;
    restoreSelection(gdi, hDC);
  }

  if (lowerCol != -1  && upperCol != -1 &&
       lowerRow != -1  && upperRow != -1 &&
       (forceDraw || modified)) {
    TableCell &c1 = Data[lowerRow].cells[lowerCol];
    TableCell &c2 = Data[upperRow].cells[upperCol];
    RECT rc;
    rc.top = min(c1.absPos.top, c2.absPos.top) + gdi.OffsetY;
    rc.left = min(c1.absPos.left, c2.absPos.left) + gdi.OffsetX;
    rc.right = max(c1.absPos.right, c2.absPos.right) + 1 + gdi.OffsetX;
    rc.bottom = max(c1.absPos.bottom, c2.absPos.bottom) + gdi.OffsetY;

    if (modified) {
      int cx=rc.right-rc.left + 1;
      int cy=rc.bottom-rc.top + 1;

      clearSelectionBitmap(&gdi, hDC);

      lastCell = rc;
      int x = lastCell.left - 1 - gdi.OffsetX;
      int y = lastCell.top - 1 - gdi.OffsetY;
      int maxX, maxY;
      gdi.getTargetDimension(maxX, maxY);

      if (x<=0 || y<=0 || (x+cx)>=maxX || (y+cy)>=maxY) {
        partialCell = true;
      }
      else {
        hdcCompatibleCell = CreateCompatibleDC(hDC);
        hbmStoredCell = CreateCompatibleBitmap(hDC, cx, cy);

        // Select the bitmaps into the compatible DC.
        SelectObject(hdcCompatibleCell, hbmStoredCell);
        BitBlt(hdcCompatibleCell, 0, 0, cx, cy, hDC, x, y, SRCCOPY);
        partialCell = false;
      }
    }

    SelectObject(hDC, GetStockObject(NULL_BRUSH));
    SelectObject(hDC, GetStockObject(DC_PEN));
    SetDCPenColor(hDC, RGB(0,0, 128));
    Rectangle(hDC, rc.left - gdi.OffsetX, rc.top - gdi.OffsetY,
                   rc.right - gdi.OffsetX, rc.bottom - gdi.OffsetY);

    lowerColOld = lowerCol;
    upperColOld = upperCol;
    lowerRowOld = lowerRow;
    upperRowOld = upperRow;
  }
}

void Table::scrollToCell(gdioutput &gdi, int row, int col) {
  if (size_t(row) >= Data.size() || size_t(col) >= Data[row].cells.size())
    return;
  const RECT &rc = Data[row].cells[col].absPos;
  int maxX, maxY;
  gdi.getTargetDimension(maxX, maxY);
  int xo = gdi.OffsetX;
  int yo = gdi.OffsetY;
  if (rc.right > maxX) {
    xo = gdi.OffsetX + (rc.right - maxX) + 10;
  }
  else if (rc.left < 0) {
    xo = gdi.OffsetX + rc.left - 10;
  }

  if (rc.bottom > maxY) {
    yo = gdi.OffsetY + (rc.bottom - maxY) + 10;
  }
  else if (rc.top < 0) {
    yo = gdi.OffsetY + rc.top - 10;
  }

  if (xo != gdi.OffsetX || yo != gdi.OffsetY) {
    gdi.setOffset(xo, yo, true);
    //gdi.refreshFast();
    //Sleep(300);
  }
}

void Table::print(gdioutput &gdi, HDC hDC, int dx, int dy)
{
  vector<int> widths(columns.size());
  vector<bool> skip(columns.size(), true);
  int rh = 0;

  for (size_t j=0;j<columns.size();j++) {
    int i=columns[j];
    TextInfo ti;
    ti.format = boldSmall;
    ti.text = Titles[i].name;
    gdi.calcStringSize(ti, hDC);
    widths[j] = max<int>(widths[j], ti.textRect.right-ti.textRect.left);
    rh = max<int>(rh, ti.textRect.bottom-ti.textRect.top);
  }
  const int extra = 10;

  for (size_t j=0;j<columns.size();j++) {
    for (size_t k=2; k<sortIndex.size(); k++) {
      TableRow &tr=Data[sortIndex[k].index];
      const wstring &ct = tr.cells[columns[j]].contents;
      if (!ct.empty()) {
        skip[j] = false;
        TextInfo ti;
        ti.format = fontSmall;
        ti.text = ct;
        gdi.calcStringSize(ti, hDC);
        widths[j] = max<int>(widths[j],
          int(1.1*(ti.textRect.right-ti.textRect.left)+extra));
        rh = max<int>(rh, ti.textRect.bottom-ti.textRect.top);
      }
    }
  }

  rh = int(rh*1.3);
  vector<int> adj_xp(columns.size());
  int w = widths[0];
  for (size_t j=1;j<columns.size();j++)
    if (!skip[j]) {
      adj_xp[j] = adj_xp[j-1] + w +5;
      w = widths[j];
    }
    else
      adj_xp[j] = adj_xp[j-1];

  for (size_t j=0;j<columns.size();j++) {
    int yp=dy+rh;
    int xp=dx+adj_xp[j];
    int i=columns[j];
    if (!skip[j])
      gdi.addString("", yp, xp, boldSmall, Titles[i].name);
  }

  for (size_t k=2; k<sortIndex.size(); k++) {
    TableRow &tr=Data[sortIndex[k].index];
    int yp=dy+rh*k;

    for (size_t j=0;j<columns.size();j++) {
      if (!skip[j]) {
        int xp=dx+adj_xp[j];
        int i=columns[j];

        if (!tr.cells[i].contents.empty())
          gdi.addString("", yp, xp, fontSmall, tr.cells[i].contents, widths[j]);
      }
    }
  }
}

bool Table::UpDown(gdioutput &gdi, int direction)
{
  return false;
}

bool Table::tabFocus(gdioutput &gdi, int direction)
{
  if (hEdit) {
    DestroyWindow(hEdit);
    drawFilterLabel=false;
    gdi.refresh();
    return true;
  }

  return false;
}

void Table::setTableText(gdioutput &gdi, int editRow, int editCol, const wstring &bf) {
  if (size_t(editRow) >= Data.size() || size_t(editCol) >= Data[editRow].cells.size())
    throw std::exception("Index out of bounds");

  wstring output;
  TableCell &cell=Data[editRow].cells[editCol];
  cell.owner->inputData(cell.id, bf, 0, output, false);
  cell.contents = output;
  if (hEdit != 0)
    DestroyWindow(hEdit);
  hEdit=0;
  reloadRow(Data[editRow].id);
  RECT rc;
  getRowRect(editRow, rc);
  InvalidateRect(gdi.getHWNDTarget(), &rc, false);
}

const wstring &Table::getTableText(gdioutput &gdi, int editRow, int editCol) {
  if (size_t(editRow) >= Data.size() || size_t(editCol) >= Data[editRow].cells.size())
    throw std::exception("Index out of bounds");

  string output;
  TableCell &cell=Data[editRow].cells[editCol];

  return cell.contents;
}


bool Table::enter(gdioutput &gdi)
{
  if (hEdit) {
    if (unsigned(editRow)<Data.size() && unsigned(editCol)<Titles.size()) {
      wchar_t bf[1024];
      GetWindowText(hEdit, bf, 1024);

      if (editRow>=2) {
        string cmd;
        if (gdi.getRecorder().recording())
          cmd = "setTableText(" + itos(editRow) + ", " + itos(editCol) + ", \"" + gdi.narrow(bf) + "\");"; 
        setTableText(gdi, editRow, editCol, bf);
        gdi.getRecorder().record(cmd);
        return true;
      }
      else if (editRow==1) {//Filter
        filter(editCol, bf);
        DestroyWindow(hEdit);
        hEdit=0;
        drawFilterLabel = false;
        gdi.refresh();
        return true;
      }
    }
  }
  else if (gdi.hasField(tId)) {
    ListBoxInfo lbi;
    gdi.getSelectedItem(tId, lbi);

    if (lbi.isCombo()) {
      if (size_t(selectionRow) < Data.size() && size_t(selectionCol) < Titles.size()) {
        selection(gdi, lbi.text, lbi.data);
      }
    }
  }
  return false;
}

void Table::escape(gdioutput &gdi)
{
  if (hEdit) {
    DestroyWindow(hEdit);
    hEdit = 0;
  }
  gdi.removeControl(tId);
  drawFilterLabel=false;
  gdi.refresh();
}

bool Table::inputChange(gdioutput &gdi, HWND hdt)
{
  if (hEdit==hdt) {

    if (drawFilterLabel) {
      wchar_t bf[256];
      GetWindowText(hEdit, bf, 256);
      filter(editCol, bf);
      updateDimension(gdi);
      gdi.refresh();
    }

    return true;
  }
  return false;
}

int Table::getColumn(int x, bool limit) const
{
  if (columns.empty())
    return -1;

  if (x<xpos[0]) {
    if (!limit)
      return -1;
    else
      return columns[0];
  }

  for(size_t k=0;k<columns.size();k++) {
    if (x<xpos[k+1])
      return columns[k];
  }
  if (!limit)
    return -1;
  else
    return columns.back();
}

int Table::getRow(int y, bool limit) const
{
  int r=(y-table_yp)/rowHeight-1;
  if (limit) {
    if (r<0)
      r=0;
    else if (r>=int(sortIndex.size()))
      r = sortIndex.size() - 1;
  }
  if (r>=0 && r<int(sortIndex.size()))
    return sortIndex[r].index;
  else return -1;
}

void Table::reloadRow(int rowId)
{
  int index;
  if (!idToRow.lookup(rowId, index))
    throw std::exception("Row not found.");

  if (index >= int(Data.size()))
    throw std::exception("Index out of bounds.");

  TableRow &row=Data[index];
  oBase *obj = row.getObject();
  if (obj) {
    TableUpdateInfo tui;
    tui.object = obj;
    tui.id = rowId;
    oe->generateTableData(internalName, *this, tui);
  }
}

vector<Table::ColSelection> Table::getColumns() const
{
  vector<Table::ColSelection> cols(Titles.size());

  // Get selected columns
  for (size_t k=0; k<columns.size(); k++) {
    cols[k].name = Titles[columns[k]].name;
    cols[k].selected = true;
    cols[k].index = columns[k];
  }
  size_t j = columns.size();
  // Get remaining columns
  for (size_t k=0; k<Titles.size(); k++) {
    if (count(columns.begin(), columns.end(), k) == 0) {
      cols[j].name = Titles[k].name;
      cols[j].selected = false;
      cols[j].index = k;
      j++;
    }
  }
  /*
  // Get all columns
  for (size_t k=0; k<cols.size(); k++)
    cols[k].first = Titles[k].name;

  // Mark shown columns as selected
  for (size_t k=0; k<columns.size(); k++)
    cols[columns[k]].second = true;
*/
  return cols;
}

void Table::selectColumns(const std::set<int> &sel)
{
  vector<int> newcols;

  for (size_t k=0; k<columns.size(); k++) {
    if (sel.count(columns[k])==1) {
      newcols.push_back(columns[k]);
    }
  }

  std::set<int>::const_iterator it = sel.begin();

  while(it != sel.end()) {
    if (count(newcols.begin(), newcols.end(), *it)==0)
      newcols.push_back(*it);
    ++it;
  }

  swap(columns, newcols);
  doAutoSelectColumns = false;
}

void Table::resetColumns()
{
  columns.resize(Titles.size());

  for (size_t k=0;k<columns.size();k++)
    columns[k] = k;

  for (size_t k=0;k<nTitles;k++)
    filter(k, L"", false);

  doAutoSelectColumns = false;
}

void Table::update()
{
  for (auto &dd : dataDefiners)
    dd.second->prepare(oe);
  int oldSort = PrevSort;
  Data.clear();
  sortIndex.clear();
  idToRow.clear();

  clearCellSelection(0);

  if (generator == 0) {
    TableUpdateInfo tui;
    oe->generateTableData(internalName, *this, tui);
  }
  else {
    generator(*this, generatorPtr);
  }

  //Refilter all
  for (size_t k=0;k<nTitles;k++)
    filter(k, Titles[k].filter, true);

  PrevSort = -1;
  if (oldSort != -1)
    sort(oldSort);
  commandLock = false; // Reset lock
}

void Table::getExportData(int col1, int col2, int row1, int row2, wstring &html, wstring &txt) const
{
  html = L"<html><table>";
  txt = L"";

  for (size_t k = row1; k<=size_t(row2); k++) {
    if ( k >= sortIndex.size())
      throw std::exception("Index out of range");
    const TableRow &tr = Data[sortIndex[k].index];
    html += L"<tr>";
    for (size_t j = col1; j<= size_t(col2); j++) {
      if ( j >= columns.size())
        throw std::exception("Index out of range");
      int col = columns[j];
      const TableCell &cell = tr.cells[col];
      html += L"<td>" + cell.contents + L"</td>";
      if (j == col1)
        txt += cell.contents;
      else
        txt += L"\t" + cell.contents;
    }
    txt += L"\r\n";
    html += L"</tr>";
  }

  html += L"</table></html>";
}

void Table::getRowRange(int &rowLo, int &rowHi) const {
  int row1 = -1, row2 = -1;
  for (size_t k = 0; k < sortIndex.size(); k++) {
    if (upperRow == sortIndex[k].index)
      row1 = k;
    if (lowerRow == sortIndex[k].index)
      row2 = k;
  }
  rowLo = min(row1, row2);
  rowHi = max(row1, row2);
}

void Table::getColRange(int &colLo, int &colHi) const {
  int col1 = -1, col2 = -1;
  for (size_t k = 0; k < columns.size(); k++) {
    if (upperCol == columns[k])
      col1 = k;
    if (lowerCol == columns[k])
      col2 = k;
  }
  colLo = min(col1, col2);
  colHi = max(col1, col2);
}

void Table::exportClipboard(gdioutput &gdi)
{
  wstring str;// = "<html><table><tr><td>a</td><td>b</td></tr></table></html>";
  wstring txt;

  int col1 = -1, col2 = -1;
  getColRange(col1, col2);
  /*for (size_t k = 0; k < columns.size(); k++) {
    if (upperCol == columns[k])
      col1 = k;
    if (lowerCol == columns[k])
      col2 = k;
  }*/

  int row1 = -1, row2 = -1;
  getRowRange(row1, row2);
  /*for (size_t k = 0; k < sortIndex.size(); k++) {
    if (upperRow == sortIndex[k].index)
      row1 = k;
    if (lowerRow == sortIndex[k].index)
      row2 = k;
  }*/

  if (col1 == -1 || col2 == -1 || row1 == -1 || row2 == -1)
    return;

  getExportData(min(col1, col2), max(col1, col2),
                min(row1, row2), max(row1, row2), str, txt);
  
  string htmlUTF = gdi.toUTF8(str);
  gdi.copyToClipboard(htmlUTF, txt);

  /*if (OpenClipboard(gdi.getHWND()) != false) {

    EmptyClipboard();

    const char *HTML = str.c_str();
    size_t len = str.length() + 1;

    size_t bsize = len*2;
    char *bf = new char[bsize];

    //Convert to UTF-8
    WORD *wide = new WORD[len];
    MultiByteToWideChar(CP_ACP, 0, HTML, -1, LPWSTR(wide), len);//To Wide
    memset(bf, 0, bsize);
    WideCharToMultiByte(CP_UTF8, 0, LPCWSTR(wide), -1, bf, bsize, NULL, NULL);
    delete[] wide;

    len = strlen(bf) + 1;

    const char cbd[]=
      "Version:0.9\n"
      "StartHTML:%08u\n"
      "EndHTML:%08u\n"
      "StartFragment:%08u\n"
      "EndFragment:%08u\n";

    char head[256];
    sprintf_s(head, cbd, 1,0,0,0);

    int offset=strlen(head);
    //Fill header with relevant information
    int ho_start = offset;
    int ho_end = offset + len;
    sprintf_s(head, cbd, offset,offset+len,ho_start,ho_end);


    HANDLE hMem=GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, offset+len);
    LPVOID data=GlobalLock(hMem);

    memcpy(LPSTR(data), head, offset);
    memcpy(LPSTR(data)+offset, bf, len);

    GlobalUnlock(hMem);

    // Text format
    HANDLE hMemText = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, txt.length()+1);
    LPVOID dataText=GlobalLock(hMemText);
    memcpy(LPSTR(dataText), txt.c_str() , txt.length()+1);
    GlobalUnlock(hMemText);


    UINT CF_HTML = RegisterClipboardFormat("HTML format");

    SetClipboardData(CF_HTML, hMem);
    SetClipboardData(CF_TEXT, hMemText);

    delete[] bf;
    CloseClipboard();
  }*/
}


void Table::importClipboard(gdioutput &gdi)
{
  if (!canPaste())
    throw std::exception("Operationen stöds ej");

  wstring str;
  if (OpenClipboard(gdi.getHWNDMain()) != false) {
    
    HANDLE data = GetClipboardData(CF_UNICODETEXT);
    if (data) {
      LPVOID lptstr = GlobalLock(data);
      if (lptstr) {
        str = wstring(((wchar_t*)lptstr));
        GlobalUnlock(data);
      }
    }
    else {
      data = GetClipboardData(CF_TEXT);
      if (data) {
        LPVOID lptstr = GlobalLock(data);
        if (lptstr) {
          string strn = string(((char*)lptstr));
          str = gdi.recodeToWide(strn);
          GlobalUnlock(data);
        }
      }
    }
    CloseClipboard();
  }
  if (!str.empty()) {
    // Parse raw data
    vector< vector<wstring> > table(1);
    const wchar_t *ptr = str.c_str();
    wstring word;
    while (*ptr) {
      if (*ptr != '\t' && *ptr != '\r' && *ptr != '\n') {
        word.append(ptr, 1);
      }
      else if (*ptr == '\t') {
        table.back().push_back(word);
        word.clear();
      }
      else if (*ptr == '\n') {
        table.back().push_back(word);
        table.push_back(vector<wstring>());
        word.clear();
      }
      ++ptr;
    }
    if (!word.empty())
      table.back().push_back(word);
    else if (table.back().empty())
      table.pop_back();

    if (table.empty())
      return;

    int rowS = 2;
    int colS = 0;

    size_t tw = 0;
    for (size_t k = 0; k<table.size(); k++)
      tw = max(tw, table[k].size());

    if (tw > columns.size())
      throw std::exception("Antalet columner i urklippet är större än antalet kolumner i tabellen.");

    if (upperRow == -1) {
      if (!gdi.ask(L"Vill du klistra in X nya rader i tabellen?#"+itow(table.size())))
        return;
      rowS = sortIndex.size(); // Add new rows
    }
    else {
      int col1 = -1, col2 = -1;
      getColRange(col1, col2);
      int row1 = -1, row2 = -1;
      getRowRange(row1, row2);

      if ( (row1 + table.size()) > sortIndex.size() )
        throw std::exception("Antalet rader i urklipp får inte plats i selektionen.");

      if ( (col1 + tw) > columns.size() )
        throw std::exception("Antalet kolumner i urklipp får inte plats i selektionen.");

      bool wrongSize = false;

      if (row1 != row2 && (row2 - row1 + 1) != table.size())
        wrongSize = true;

      if (col1 != col2 && (col2 - col1 +1 ) != tw)
        wrongSize = true;

      if (wrongSize && !gdi.ask(L"Selektionens storlek matchar inte urklippets storlek. Klistra in i alla fall?"))
        return;

      rowS = row1;
      colS = col1;
    }

    bool addedRow = false;

    for (size_t k = 0; k<table.size(); k++) {
      if (table[k].empty())
        continue;
      if ( (rowS + k) > sortIndex.size()) {
        throw std::exception("Index out of range");
      }
      else if ( (rowS + k) == sortIndex.size()) {
         // Add data
         TableUpdateInfo tui;
         tui.doAdd = true;
         oe->generateTableData(internalName, *this, tui);
         addedRow = true;
         //sortIndex.push_back(TableSortIndex(Data.size()-1, ""));
      }

      TableRow &tr = Data[sortIndex[rowS + k].index];
      for (size_t j = 0; j<table[k].size(); j++) {
        if ( (colS + j) >= columns.size())
          throw std::exception("Index out of range");
        int col = columns[colS + j];

        TableCell &cell=tr.cells[col];
        wstring output;

        size_t index = 0;

        if (cell.type==cellSelection || cell.type==cellCombo) {
          vector< pair<wstring, size_t> > out;
          size_t selected = 0;
          cell.owner->fillInput(cell.id, out, selected);
          index = -1;
          for (size_t i = 0; i<out.size() && index == -1; i++) {
            if (_wcsicmp(out[i].first.c_str(), table[k][j].c_str()) == 0)
              index = out[i].second;
          }
        }
        try {
          if (index != -1) {
            cell.owner->inputData(cell.id, table[k][j], index, output, false);
            cell.contents = output;
          }
          else if (cell.type == cellCombo) {
            cell.owner->inputData(cell.id, table[k][j], index, output, false);
            cell.contents = output;
          }
        }
        catch (const meosException &ex) {
          wstring msg(ex.wwhat());
        }
        catch(const std::exception &ex) {
          string msg(ex.what());
        }
      }
    }

    if (addedRow) {
      TableUpdateInfo tui;
      tui.doRefresh = true;
      oe->generateTableData(internalName, *this, tui);

      updateDimension(gdi);
      int dx,dy;
      getDimension(gdi, dx, dy, true);
      gdi.scrollTo(0, dy + table_yp + gdi.OffsetY);
    }
    gdi.refresh();
  }
}

int Table::deleteRows(int row1, int row2)
{
  if (!canDelete())
    throw std::exception("Operationen stöds ej");

  int failed = 0;
  for (size_t k = row1; k<=size_t(row2); k++) {
    if ( k >= sortIndex.size())
      throw std::exception("Index out of range");
    const TableRow &tr = Data[sortIndex[k].index];
    oBase *ob = tr.cells[0].owner;
    if (!ob)
      throw std::exception("Null pointer exception");
    if (ob->canRemove())
      ob->remove();
    else
      failed++;
  }

  clearCellSelection(0);
  clearSelectionBitmap(0,0);
  update();
  return failed;
}

void Table::insertRow(gdioutput &gdi) {
  if (!canInsert())
    throw std::exception("Operationen stöds ej");

  TableUpdateInfo tui;
  tui.doAdd = true;
  tui.doRefresh = true;
  oe->generateTableData(internalName, *this, tui);
  int dx,dy;
  updateDimension(gdi);
  getDimension(gdi, dx, dy, true);
  gdi.scrollTo(0, dy + table_yp + gdi.OffsetY);
}

void Table::autoAdjust(gdioutput &gdi) {
  initEmpty();
  if (Titles.empty())
    return;
  HDC hDC = GetDC(gdi.getHWNDTarget());
  RECT rc = {0,0,0,0};
  TextInfo ti;
  wstring filterText = lang.tl("Urval...");
  wstring filterName = lang.tl("Namn");
  ti.format = 0;
  gdi.formatString(ti, hDC);
  int sum = 0;
  for (size_t k = 0; k<Titles.size(); k++) {
    int w = 20;
    int minlen = 0;
    int diff = Titles[k].isnumeric ? 0 : 4;
    size_t dsize = Data.size();
    int sample = max<size_t>(1, dsize/1973);
    int sameCount = 0;
    for (size_t r = 0; r < dsize; r+=sample) {
      const TableCell &c = Data[r].cells[k];
      if (r==0 && c.contents == filterName)
        w = max(w, 100);

      const wstring &str = r != 1 ? c.contents : filterText;
      int len = str.length();
      if (len == minlen) {
        sameCount++;
        if (sameCount > 40)
          continue;
      }

      if (len > minlen - diff) {
        sameCount = 0;
        if (Titles[k].isnumeric && r>2)
          DrawText(hDC, (str + L"55").c_str(), len, &rc, DT_CALCRECT|DT_NOPREFIX);
        else
          DrawText(hDC, str.c_str(), len, &rc, DT_CALCRECT|DT_NOPREFIX);
        w = max<int>(w, rc.right - rc.left);
        if (r>2)
          minlen = max(len, minlen);
      }
    }
    Titles[k].baseWidth = int( (w + 25)/ gdi.getScale());
  }
  if (columns.empty())
    columns.push_back(0);

  for (size_t k = 0; k<columns.size(); k++) {
     sum += Titles[columns[k]].baseWidth;
  }

  if (sum<300) {
    int dx = (310-sum) / columns.size();
    for (size_t k = 0; k<columns.size(); k++) {
      Titles[columns[k]].baseWidth += dx;
    }
  }
  ReleaseDC(gdi.getHWNDTarget(), hDC);
  updateDimension(gdi);
}

void Table::autoSelectColumns() {
  initEmpty();
  vector<bool> empty(Titles.size(), true);
  int nonEmpty = 0;

  // Filter away empty and all-equal columns
  for (size_t k = 0; k<Titles.size(); k++) {

    if (Data.size() < 3) {
      if (Data.size() > 2 && Data[2].cells[k].type == cellAction)
        nonEmpty++;
      empty[k] = false;
    }
    else {
      if (Data[2].cells[k].type == cellAction) {
        nonEmpty++;
        empty[k] = false;
      }
      else {
        const wstring &first = Data.size() > 3 ?
          Data[2].cells[k].contents : _EmptyWString;

        for (size_t r = 2; r<Data.size(); r++) {
          const wstring &c = Data[r].cells[k].contents;
          if (!c.empty() && c != first) {
            nonEmpty++;
            empty[k] = false;
            break;
          }
        }
      }
    }
  }

  if (nonEmpty > 1) {
    columns.clear();
    wstring id = lang.tl("Id");
    wstring mod = lang.tl("Ändrad");
    for (size_t k = 0; k<empty.size(); k++) {
      if (!empty[k] && wcscmp(Titles[k].name, id.c_str()) != 0 && wcscmp(Titles[k].name, mod.c_str()) != 0)
        columns.push_back(k);
    }
  }

  doAutoSelectColumns = true;
}

void Table::getRowRect(int row, RECT &rc) const {
  rc.left = 100000;
  rc.right = -100000;
  if (columns.empty())
    return;
  rc.top = Data[row].cells[columns[0]].absPos.top;
  rc.bottom = Data[row].cells[columns[0]].absPos.bottom;

  for (size_t k = 0; k<columns.size(); k++) {
    rc.left = min(rc.left, Data[row].cells[columns[k]].absPos.left);
    rc.right = max(rc.right, Data[row].cells[columns[k]].absPos.right);
  }
}

void Table::updateDimension(gdioutput &gdi) {
  int dx, dy;
  getDimension(gdi, dx, dy, true);
  gdi.adjustDimension(max(t_xpos + dx + TableXMargin, t_maxX), max(t_ypos + dy + TableYMargin, t_maxY));
}

int Table::getNumDataRows() const {
  return Data.size() - 2;
}

void TableRow::setObject(oBase &obj) {
  ob = &obj;
  for (size_t k = 0; k < cells.size(); k++) {
    if (cells[k].owner != 0)
      cells[k].owner = &obj;
  }
}

void Table::addDataDefiner(const string &key, const oDataDefiner *definer) {
  dataDefiners[key] = definer;
}
