/************************************************************************
    MeOS - Orienteering Software
    Copyright (C) 2009-2011 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
    Stigbergsvägen 7, SE-75242 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>

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

Table::Table(oEvent *oe_, int rowH, 
             const string &name, const string &iName)
{
  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;

  doAutoSelectColumns = true;
}

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->getTarget());
    clearSelectionBitmap(gdi, hDC);
    ReleaseDC(gdi->getTarget(), hDC);
  }
}

int Table::addColumn(const string &Title, int width, bool isnum, bool formatRight)
{
	RowInfo ri;
	strcpy_s(ri.name, lang.tl(Title).c_str());
	ri.baseWidth = width;
  ri.width = 0;
	ri.isnumeric = isnum;
  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);
  sortIndex.reserve(siz);
}
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="...";

      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 string &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 string &filt, bool forceFilter)
{
  const string &oldFilter=Titles[col].filter;
  vector<TableSortIndex> baseIndex;

  if (filt==oldFilter && (!forceFilter || filt.empty()))
    return;
  else if (strncmp(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());
    TableSortIndex tsi;
    for (size_t k=0; k<Data.size();k++) {      
      tsi.index=k;
      sortIndex[k]=tsi;
    }

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

  char filt_lc[1024];
  strcpy_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]);
  }
}

void Table::sort(int col)
{
  if(sortIndex.size()<2)
    return;
  currentSortColumn=col;
	if (PrevSort!=col && PrevSort!=-(10+col)) {
	  if(Titles[col].isnumeric)
	  {
      char bf[16];
		  //Nummeric data
      for(size_t k=2; k<sortIndex.size(); k++){					
        sprintf_s(bf, "%08X", atoi(Data[sortIndex[k].index].cells[col].contents.c_str()));
        sortIndex[k].key=bf;
		  }
    }
    else {		
      for(size_t k=2; k<sortIndex.size(); k++){					      
        sortIndex[k].key=Data[sortIndex[k].index].cells[col].contents;
		  }
    }
    std::stable_sort(sortIndex.begin()+2, sortIndex.end());
		PrevSort=col;
	} 
	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::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);

  //SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT));
  TextInfo ti;
  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::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.getTarget();

  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.getTarget();
      HDC hDC=GetDC(hWnd);
      stopMoveCell(hDC, cell, x-startX, y-startY);
      ReleaseDC(hWnd, hDC);
      //return true;
    }

    if (highRow==0 && colSelected==highCol) {
      colSelected=-1;
      sort(highCol);
      gdi.refresh();
      mouseMove(gdi, x, y);
      return true;
    }
    else {
      moveColumn(colSelected, highCol);
      InvalidateRect(gdi.getTarget(), 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);
    //TableCell &cell = *static_cast<TableCell *>(lbi.getExtra());
    Table &t = *static_cast<Table *>(lbi.getExtra());
    t.selection(*gdi, lbi.text, lbi.data);
  }
  return 0;
}

void Table::selection(gdioutput &gdi, const string &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;
 string output = cell.contents;
 cell.owner->inputData(cell.id, text, data, output, false);
 cell.contents = output;
 reloadRow(id);
 RECT rc;
 getRowRect(selectionRow, rc);
 InvalidateRect(gdi.getTarget(), &rc, false);
}

#ifndef MEOSDB

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

  try {
    if (code == KC_COPY)
      exportClipboard(gdi);
    else if (code == KC_PASTE) {
      importClipboard(gdi);
    }else if (code == KC_DELETE) {
      deleteSelection(gdi);
    }
    else if (code == KC_REFRESH) {
      update();
      autoAdjust(gdi);
      gdi.refresh();
    }
    else if (code == KC_INSERT) {
      insertRow(gdi);
      gdi.refresh();
    }
    else if (code == KC_PRINT) {
      gdioutput gdiPrint(gdi.getScale());	
	    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("Vill du radera X rader från tabellen?#" + itos(r2-r1+1))) 
      return false;
    int failed = deleteRows(r1, r2);
    gdi.refresh();
    if (failed > 0)
      gdi.alert("X rader kunde inte raderas.#" + itos(failed));
  }
  return true;
}

void Table::hide(gdioutput &gdi) {
  destroyEditControl(gdi);
  clearCellSelection(0);
  ReleaseCapture();
  Data.clear();
  sortIndex.clear();
  idToRow.clear();
}

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

  if (hEdit) {
    if (!enter(gdi))
      return false;
    
    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) {
  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, "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.getTarget(),
        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.getHWND());
    SetCapture(gdi.getTarget());
    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.canEdit) {
    MessageBeep(-1);
    return true;
  }
  ReleaseCapture();

  editRow = row;
  editCol = col;

  RECT &rc=cell.absPos;

  if (cell.type==cellAction) {
    gdi.makeEvent("CellAction", cell.id, cell.owner);
    return true;
  }
  if (cell.type==cellSelection) {
    selectionRow = row;
    selectionCol = col;
    gdi.addSelection(rc.left+gdi.OffsetX, rc.top+gdi.OffsetY, tId, 
            int(max<int>((rc.right-rc.left+1), 170)/gdi.scale), (rc.bottom-rc.top)*10, 
            tblSelectionCB).setExtra(this); 
    vector< pair<string, size_t> > out;
    size_t selected = 0;
    cell.owner->fillInput(cell.id, out, selected);//gdi, tId);
    gdi.addItem(tId, out);
    gdi.selectItemByData(tId, selected);
    return true;
  }
  else if (cell.type==cellEdit) {
    hEdit=CreateWindowEx(0, "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.getTarget(),
      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 Table::draw(gdioutput &gdi, HDC hDC, int dx, int dy, const RECT &screen) 
{
  gdi.resetLast();
  initEmpty();

  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));
    char bf[256];
    string info = lang.tl(string("sortering: X, antal rader: Y#") + Titles[currentSortColumn].name + "#" + itos(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;
      char tbf[2];
      tbf[0]=Titles[editCol].name[0];
      tbf[1]=0;
      CharLower(tbf);
      string filter = lang.tl("Urval %c%s:")+" ";
      sprintf_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) {
    string 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));

  for (size_t k1=2; k1<sortIndex.size(); 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 (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.getTarget(), &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 string &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;
}

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

      if (editRow>=2) {
        TableCell &cell=Data[editRow].cells[editCol];
        string output;

        try {
          cell.owner->inputData(cell.id, bf, 0, output, false);
          cell.contents=output;
          DestroyWindow(hEdit);
          hEdit=0;
          reloadRow(Data[editRow].id);
          RECT rc;
          getRowRect(editRow, rc);
          InvalidateRect(gdi.getTarget(), &rc, false);
          return true;
        }
        catch(const std::exception &ex) {         
          string msg(ex.what());
          gdi.alert(msg);
        }
      }
      else if(editRow==1) {//Filter
        filter(editCol, bf);
        DestroyWindow(hEdit);
        hEdit=0;
        drawFilterLabel = false;
        gdi.refresh();
        return true;
      }
    }
  }
  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) {
      char bf[256];
      GetWindowText(hEdit, bf, 256);
      filter(editCol, bf);
      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<pair<string, bool>> Table::getColumns() const
{
  vector<pair<string, bool>> cols(Titles.size());

  // Get selected columns
  for (size_t k=0; k<columns.size(); k++) {
    cols[k].first = Titles[k].name;
    cols[k].second = true; 
  }
  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].first = Titles[k].name;
      cols[j].second = false; 
      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, "", false);

  doAutoSelectColumns = false;
}

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

  clearCellSelection(0);

  TableUpdateInfo tui;
  oe->generateTableData(internalName, *this, tui);

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

  commandLock = false; // Reset lock
}

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

  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 += "<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 += "<td>" + cell.contents + "</td>";
      if (j == col1)
        txt += cell.contents;
      else
        txt += "\t" + cell.contents;
    }
    txt += "\r\n";
    html += "</tr>";
  }

  html += "</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)
{
  string str;// = "<html><table><tr><td>a</td><td>b</td></tr></table></html>";
  string 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);

  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);
			/*	
		char *start=FindMemEx("<!--StartFragment-->", HTML, len);
		char *end=FindMemEx("<!--EndFragment-->", HTML, len) - strlen("<!--StartFragment-->");
		
		DWORD ho_start=start-HTML+offset;
		DWORD ho_end=end-HTML+offset;
		*/
		//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");

  string str;
  if(OpenClipboard(gdi.getHWND()) != false) {
    HANDLE data = GetClipboardData(CF_TEXT);
    if (data) {
      LPVOID lptstr = GlobalLock(data); 
      if (lptstr) { 
        str = string(((char*)lptstr));
        GlobalUnlock(data); 
      } 
    }
		CloseClipboard();
	}
  if (!str.empty()) {
    // Parse raw data
    vector< vector<string> > table(1);
    const char *ptr = str.c_str();
    string 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<string>());
        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("Vill du klistra in X nya rader i tabellen?#"+itos(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("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];
        string output;

        size_t index = 0;

        if (cell.type==cellSelection) {
          vector< pair<string, 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 (_stricmp(out[i].first.c_str(), table[k][j].c_str()) == 0)
              index = out[i].second;
          }
        }

        if (index != -1) {
          try {
            cell.owner->inputData(cell.id, table[k][j], index, output, false); 
            cell.contents = output;
          }
          catch(const std::exception &ex) {         
            string msg(ex.what());
            //gdi.alert(msg);
          }
        }
      }
    }

    if (addedRow) {
      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;
  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.getTarget());
  RECT rc = {0,0,0,0};
  TextInfo ti;
  string filterText = lang.tl("Urval...");
  string 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 = 8;
    for (size_t r = 0; r<Data.size(); r++) {
      if (r==0 && Data[r].cells[k].contents == filterName)
        w = max(w, 100);

      string &str = r != 1 ? Data[r].cells[k].contents : filterText;
      int len = str.length();
      if (len > minlen/2) {        
        DrawText(hDC, str.c_str(), len, &rc, DT_CALCRECT|DT_NOPREFIX);
        w = max<int>(w, rc.right - rc.left);
        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.getTarget(), 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++) {
    const string &first = Data.size() > 3 ? Data[2].cells[k].contents : "";
    for (size_t r = 2; r<Data.size(); r++) {
      const string &c = Data[r].cells[k].contents;
      if (!c.empty() && c != first) {
        nonEmpty++;
        empty[k] = false;
        break;
      }
    }
  }

  if(nonEmpty > 1) {
    columns.clear();
    string id = lang.tl("Id");
    string mod = lang.tl("Ändrad");
    for (size_t k = 0; k<empty.size(); k++) {
      if (!empty[k] && strcmp(Titles[k].name, id.c_str()) != 0 && strcmp(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, false);
  gdi.updatePos(t_xpos, t_ypos, dx + TableXMargin, dy + TableYMargin);
}
