/************************************************************************ MeOS - Orienteering Software Copyright (C) 2009-2020 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 . 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 #include #include #include "oEvent.h" #include "localizer.h" #include #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; clearCellSelection(0); tableProp = -1; dataPointer = -1; clearOnHide = true; doAutoSelectColumns = true; generator = 0; generatorPtr = 0; } Table::~Table(void) { if (hEdit) DestroyWindow(hEdit); } 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) { return addColumn(lang.tl(Title).c_str(), width, isnum, formatRight); } int Table::addColumn(const wstring &translatedTitle, int width, bool isnum, bool formatRight) { ColInfo ri; wcscpy_s(ri.name, translatedTitle.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::iterator it_s=find(columns.begin(), columns.end(), src); vector::iterator it_t=find(columns.begin(), columns.end(), target); if (it_s!=columns.end()) { if (it_s0 && 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;i0) 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.ownerRef = owner.getReference(); 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 baseIndex; if (filt==oldFilter && (!forceFilter || filt.empty())) return; else if (wcsncmp(oldFilter.c_str(), filt.c_str(), oldFilter.length())==0) { if (sortIndex.empty()) return; //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 '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 '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 1) { intKey |= unsigned(strBuff[1]); } } } else { for (size_t k=2; k 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(); auto &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(); 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(2,Data.size())); dx=1; for(size_t i=0;i=rc.right || yrc.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(c, 2); } HDC hDC=GetDC(hWnd); if (unsigned(highRow)=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)= 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); 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; pair res; if (cell.hasOwner()) res = cell.getOwner()->inputData(cell.id, text, data, cell.contents, false); if (res.second) { update(); gdi.refresh(); } else { reloadRow(id); if (res.first) reloadRow(res.first); //RECT rc; //getRowRect(selectionRow, rc); InvalidateRect(gdi.getHWNDTarget(), nullptr, 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()); 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.removeWidget(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.hasOwner() ? cell.getOwner()->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 > out; size_t selected = 0; if (cell.hasOwner()) cell.getOwner()->fillInput(cell.id, out, selected); int width = 40; for (size_t k = 0; k(width, 8*out[k].first.length()); if (cell.type == cellSelection) { gdi.addSelection(rc.left+gdi.OffsetX, rc.top+gdi.OffsetY, tId, max(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((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(firstCol0 && 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(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(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= 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 widths(columns.size()); vector skip(columns.size(), true); int rh = 0; for (size_t j=0;j(widths[j], ti.textRect.right-ti.textRect.left); rh = max(rh, ti.textRect.bottom-ti.textRect.top); } const int extra = 10; for (size_t j=0;j(widths[j], int(1.1*(ti.textRect.right-ti.textRect.left)+extra)); rh = max(rh, ti.textRect.bottom-ti.textRect.top); } } } rh = int(rh*1.3); vector adj_xp(columns.size()); int w = widths[0]; for (size_t j=1;j= Data.size() || size_t(editCol) >= Data[editRow].cells.size()) throw std::exception("Index out of bounds"); wstring output; TableCell &cell=Data[editRow].cells[editCol]; if (cell.hasOwner()) cell.getOwner()->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)=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.hasWidget(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.removeWidget(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() || xpos.empty()) return -1; if (x=int(sortIndex.size())) r = sortIndex.size() - 1; } if (r>=0 && r= 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::getColumns() const { vector cols(Titles.size()); // Get selected columns for (size_t k=0; k &sel) { vector newcols; for (size_t k=0; k::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;kprepare(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"; 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""; 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""; if (j == col1) txt += cell.contents; else txt += L"\t" + cell.contents; } txt += L"\r\n"; html += L""; } html += L"
" + cell.contents + L"
"; } 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;// = "
ab
"; 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 > 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()); 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 columns.size()) throw meosException("Antalet kolumner 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 meosException("Antalet rader i urklipp får inte plats i selektionen."); if ( (col1 + tw) > columns.size() ) throw meosException("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 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= 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 > out; size_t selected = 0; if (cell.hasOwner()) cell.getOwner()->fillInput(cell.id, out, selected); index = -1; for (size_t i = 0; iinputData(cell.id, table[k][j], index, output, false); cell.contents = output; } else /*if (cell.type == cellCombo)*/ { if (cell.hasOwner()) cell.getOwner()->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].getOwner(); if (ob) { 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(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(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 empty(Titles.size(), true); int nonEmpty = 0; // Filter away empty and all-equal columns for (size_t k = 0; k 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 1) { columns.clear(); wstring id = lang.tl("Id"); wstring mod = lang.tl("Ändrad"); for (size_t k = 0; k