Source file: /~heha/ewa/Ofen/prozess.zip/msvc/o1/tableedit.cpp

#include "tableedit.h"
#include <commctrl.h>
#include <shlwapi.h>
#include "wutils.h"
#include "vector.h"
#include "plot.h"

/*************
 * tableedit *
 *************/
class tableedit{
 struct tableaxis{
  int flags;	// Spalte: Bit 1:0 = Ausrichtung links(00),rechts(01),dezimal(10),Mitte(11)
	        //         Bit 4 = Ausrichtungs-Override für Zeilen
		//         Bit 5 = Header-Spalte (grau)
		//	   Bit 6 = Spalte klebt am linken/rechten Fensterrand
		//	   Bit 7 = Spalte nicht editierbar
		// Zeile:  Bit 4 = Ausrichtungs-Override für Spalten (gewinnt über Bit 2 der Spalte)
		//	   Bit 1:0 = Ausrichtung wie oben
		//	   Bit 5 = Header-Zeile (grau, nicht editierbar)
		//	   Bit 6 = Zeile klebt am oberen/unteren Fensterrand
		//	   Bit 7 = Zeile nicht editierbar
  int width;	// ohne Rand
 };
 struct intpair{int a,b;};
 tableedit(HWND);
 ~tableedit();
 HWND wnd,edit;
 WPARAM editindex;
 WNDPROC orgeditproc;
 static LRESULT CALLBACK editsubproc(HWND,UINT,WPARAM,LPARAM);
 LRESULT editsubproc(UINT,WPARAM,LPARAM);
 HFONT deffont,boldfont;
 std::vector<tableaxis>cols,rows;
 TE_ITEM*items;	// Ausdehnung dieses Arrays je nach cols.length × rows.length!
 TE_METRICS m;	// Alles was keine Handles sind
 bool initstorage(UINT,UINT);
 bool insertcol(UINT,UINT=1);
 bool insertrow(UINT,UINT=1);
 bool deletecol(UINT,UINT=1);
 bool deleterow(UINT,UINT=1);
 WPARAM itemindex(UINT,UINT);
 WPARAM itemindex(WPARAM);
 void itemmove(TE_ITEM*&dst,const TE_ITEM*&src,WPARAM count) const;
 void itemfill(TE_ITEM*&dst,WPARAM count) const;
 void setedit(WPARAM);
 void killedit(bool save=true);
 bool okaytoedit(WPARAM);
 static int gettext(char*,TCHAR*,int);
 char hittest(POINT&);		// Bit 0: column valid, Bit 1: row valid, Bit 2: horizontal divider, Bit 3: vertical divider
 char getalign(UINT,UINT);	// effektive Ausrichtung für die Zelle
 bool getsplit(UINT,UINT);
 COLORREF getbcolor(UINT,UINT);
 COLORREF getfcolor(UINT,UINT);
 int gettext(TE_ITEM&,TCHAR*,int);
 void itemrect(UINT,UINT,Rect&);
 void itempaint(HDC,UINT,UINT,Rect&,int,TE_ITEM&);
 void itemsize(HDC,UINT,UINT,SIZE&,intpair&,TE_ITEM&);
 void textsize(HDC,char*,SIZE&,intpair&,bool);
 int space_nk(HDC,TCHAR*,int);
 void onSize(int,int);
 void onPaint(HDC);
 void setfont(HFONT);
 static LRESULT CALLBACK wndproc(HWND,UINT,WPARAM,LPARAM);
 LRESULT wndproc(UINT,WPARAM,LPARAM);
public: 
 static void init();
};

void tableedit::init() {
 static const WNDCLASS wc={
  CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS|CS_PARENTDC,
  wndproc,
  0,		// ClassExtra
  sizeof(tableedit*),// WindowExtra
  0,		// hInstance
  0,		// hIcon
  0,
  (HBRUSH)(COLOR_WINDOW+1),
  0,
  TEXT("table")
 };
 RegisterClass(&wc);
}

LRESULT CALLBACK tableedit::editsubproc(HWND Wnd,UINT msg,WPARAM wParam,LPARAM lParam) {
 tableedit*self=(tableedit*)GetWindowLongPtr(GetParent(Wnd),0);
 LRESULT r=self->editsubproc(msg,wParam,lParam);
 if (r) return r;
 return CallWindowProc(self->orgeditproc,Wnd,msg,wParam,lParam);
}

LRESULT tableedit::editsubproc(UINT msg,WPARAM wParam,LPARAM lParam) {
 switch (msg) {
  case WM_KEYDOWN: switch (wParam) {
   case VK_DELETE: // Zeile/Spalte einfügen/löschen per Tastatur
   case VK_INSERT: if (GetKeyState(VK_SHIFT)<0 || GetKeyState(VK_CONTROL)<0) goto post; break;
   case VK_LEFT: if (LOWORD(Edit_GetSel(edit))==0) goto post; break;
   case VK_RIGHT: if (HIWORD(Edit_GetSel(edit))==Edit_GetTextLength(edit)) goto post; break;
   case VK_DOWN:
   case VK_UP: post: PostMessage(wnd,WM_COMMAND,MAKELONG(wParam,EN_VSCROLL),(LPARAM)edit); return 1;
  }break;
 }
 return 0;
}

LRESULT CALLBACK tableedit::wndproc(HWND Wnd,UINT msg,WPARAM wParam,LPARAM lParam) {
 tableedit*self=(tableedit*)GetWindowLongPtr(Wnd,0);
 if (!self) {
  self=new tableedit(Wnd);
  SetWindowLong(Wnd,0,(LONG_PTR)self);
 }
 LRESULT r=self->wndproc(msg,wParam,lParam);
 if (msg==WM_NCDESTROY) delete self;
 return r;
}

void tableedit::onSize(int,int) {
}
void tableedit::onPaint(HDC dc) {
 HFONT ofont=SelectFont(dc,deffont);
// Alle Items messen und Maxima ermitteln
 std::vector<intpair>colw;
 colw.resize(cols.size());
 memset(colw.begin(),0,colw.size()*sizeof(intpair));
 for each (tableaxis col in cols) col.width=m.minW;
 TE_ITEM*itemp=items;
 for (int j=0; j<(int)rows.size(); j++) {
  tableaxis&row=rows[j];
  row.width=m.minH;
  for (int i=0; i<(int)cols.size(); i++) {
   tableaxis&col=cols[i];
   SIZE siz;
   itemsize(dc,i,j,siz,colw[i],*itemp);
   if (col.width<siz.cx) col.width=siz.cx;	// Gesamtbreite
   if (row.width<siz.cy) row.width=siz.cy;	// Höhe
   itemp++;
  }
 }
 for (int i=0; i<(int)cols.size(); i++) {
  tableaxis&col=cols[i];
  int sum=colw[i].a+colw[i].b;
  if (col.width<sum) col.width=sum;	// Gesamtbreite
// Ab jetzt wird nur noch colw[i].b benutzt - und die breiteste Zahl zentriert
  int space=col.width-sum;
  colw[i].b+=space>>1;			// Position nach links schieben
 }
 HPEN pen=CreatePen(PS_SOLID,m.borW,m.border);
 HPEN open=SelectPen(dc,pen);
 Rect r;
 GetClientRect(wnd,&r);
 HRGN hrgn=CreateRectRgn(r.left,r.top,r.right,r.bottom);
 SelectClipRgn(dc,hrgn);
 DeleteRgn(hrgn);
 POINT pt[2];
// Ränder zeichnen
 pt[0].y=r.top;
 pt[1].y=r.bottom;
 for (int x=0, i=0;; x+=cols[i++].width+m.padX+m.borW+m.padX) {
  pt[1].x=pt[0].x=x;
  Polyline(dc,pt,2);
  if (i==cols.size()) break;
 }
 pt[0].x=r.left;
 pt[1].x=r.right;
 for (int y=0, j=0;; y+=rows[j++].width+m.padY+m.borW+m.padY) {
  pt[1].y=pt[0].y=y;
  Polyline(dc,pt,2);
  if (j==rows.size()) break;
 }
 SelectPen(dc,open);
 DeletePen(pen);
// Inhalte zeichnen
 itemp=items;
 for (int y=m.borW, h, j=0; j<(int)rows.size(); y+=h+m.padY+m.borW+m.padY,j++) {
  h=rows[j].width;
  for (int x=m.borW, w, i=0; i<(int)cols.size(); x+=w+m.padX+m.borW+m.padX,i++) {
   w=cols[i].width;
   SetRect(&r,x,y,x+w+m.padX+m.padX,y+h+m.padY+m.padY);
   itempaint(dc,i,j,r,colw[i].b,*itemp);
   itemp++;
  }
 }
 SelectObject(dc,ofont);
}
int tableedit::gettext(char*s,TCHAR*buf,int buflen) {
 if (!s) return 0;
 return MultiByteToWideChar(CP_UTF8,0,s,strlen(s),buf,buflen);
}
int tableedit::gettext(TE_ITEM&item,TCHAR*buf,int buflen) {
 if (item.s!=LPSTR_TEXTCALLBACKA) return gettext(item.s,buf,buflen);
 return 0;
}
int tableedit::space_nk(HDC dc, TCHAR*buf, int l) {
 TCHAR*p=StrRChr(buf,buf+l,m.DecimalChar);	// Position des letzten Dezimaltrennzeichens
 if (p) {
  SIZE siz;
  GetTextExtentPoint32(dc,p,buf+l-p,&siz);	// Länge des Strings am Ende einschließlich Dezimaltrennzeichen
  return siz.cx;			// diesen Wert zurückgeben
 }
 return 0;
}
void tableedit::textsize(HDC dc, char*s, SIZE&sz, intpair&pair, bool split) {
 TCHAR buf[256];
 int l=gettext(s,buf,elemof(buf));
 GetTextExtentPoint32(dc,buf,l,&sz);
 if (split) {			// "pair" nur bei Dezimalpunktausrichtung beachten
  int b=space_nk(dc,buf,l);
  int a=sz.cx-b;
  if (pair.a<a) pair.a=a;	// Größen maximieren
  if (pair.b<b) pair.b=b;
 }
}
void tableedit::itemsize(HDC dc, UINT x, UINT y, SIZE&sz, intpair&pair, TE_ITEM&item) {
 sz.cx=sz.cy=0;
 if (item.s) textsize(dc,item.s,sz,pair,getsplit(x,y));
}
bool tableedit::getsplit(UINT x, UINT y) {
 int fx=cols[x].flags;
 int fy=rows[y].flags;
 if (fy&0x10) return (fy&3)==2;
 return (fx&3)==2;
}
char tableedit::getalign(UINT x, UINT y) {
 char fx=cols[x].flags;
 char fy=rows[y].flags;
 if (fy&0x10) fx=fy;		// Override
 fx&=3;
 if (fx==2) fx=1;		// TA_RIGHT vorbereiten
 return fx<<1;			// das ergibt nun TA_LEFT, TA_RIGHT und TA_CENTER
}
COLORREF tableedit::getfcolor(UINT x, UINT y) {
 if (items[itemindex(x,y)].state&TEIS_ERROR) return m.fgError;
 return GetSysColor(COLOR_WINDOWTEXT);
}
COLORREF tableedit::getbcolor(UINT x, UINT y) {
 if (items[itemindex(x,y)].state&TEIS_ERROR) return m.bgError;
 char fx=cols[x].flags;
 char fy=rows[y].flags;
 if ((fx|fy)&0x20) return m.border;
 return GetSysColor(COLOR_WINDOW);
}
void tableedit::itemrect(UINT x, UINT y, Rect&r) {	// ohne Border, aber mit Padding
 r.right=0;
 for (UINT i=0; i<=x; i++) {
  r.left=r.right+m.borW;
  r.right=r.left+m.padX+cols[i].width+m.padX;
 }
 r.bottom=0;
 for (UINT j=0; j<=y; j++) {
  r.top=r.bottom+m.borW;
  r.bottom=r.top+m.padY+rows[j].width+m.padY;
 }
}
void tableedit::itempaint(HDC dc, UINT x, UINT y, Rect&r, int xorg, TE_ITEM&item) {
 if (item.s) {
  char ali=getalign(x,y);
  SetTextAlign(dc,ali);
  TCHAR buf[256];
  int l=gettext(item,buf,elemof(buf));
  if (getsplit(x,y))
   xorg=r.width()-m.padX-xorg+space_nk(dc,buf,l);	// Ursprung nach rechts rücken, wenn Dezimaltrennzeichen vorhanden
  else switch (ali) {
   case TA_LEFT: xorg=m.padX; break;
   case TA_RIGHT: xorg=r.width()-m.padX; break;
   default: xorg=r.width()>>1;
  }
  SetTextColor(dc,getfcolor(x,y));
  SetBkColor(dc,getbcolor(x,y));	// für Textausgabe
  ExtTextOut(dc,r.left+xorg,r.top+m.padY,ETO_OPAQUE|ETO_CLIPPED,&r,buf,l,0);
 }else{
  NMCUSTOMDRAW cd;
  cd.hdr.hwndFrom=wnd;
  cd.hdr.idFrom=GetWindowLong(wnd,GWL_ID);
  cd.hdr.code=NM_CUSTOMDRAW;
  cd.dwDrawStage=CDDS_ITEM;
  cd.hdc=dc;
  CopyRect(&cd.rc,&r);
  cd.dwItemSpec=MAKELONG(y,x);
  cd.uItemState=item.state;
  cd.lItemlParam=item.lParam;
  SendMessage(GetParent(wnd),WM_NOTIFY,cd.hdr.idFrom,(LPARAM)&cd);
 }
}
WPARAM tableedit::itemindex(WPARAM wParam) {
 return itemindex(LOWORD(wParam),HIWORD(wParam));
}
WPARAM tableedit::itemindex(UINT x, UINT y) {
 if (x>=cols.size()) return -1;
 if (y>=rows.size()) return -1;
 return (WPARAM)y*cols.size()+x;
}
bool tableedit::initstorage(UINT x, UINT y) {
 if (items) {
  int l=cols.size()*rows.size();
  for (int i=0; i<l; i++) if (items[i].s) delete[] items[i].s;
  delete[] items; items=0;
 }
 tableaxis def={m.defXStyle,m.minW};
 cols.resize(x,def);
 def.flags=m.defYStyle;
 def.width=m.minH;
 rows.resize(y,def);
 WPARAM l=(WPARAM)x*y;
 if (l) {
  items=new TE_ITEM[l];
  if (!items) return false;
  TE_ITEM*itemp=items;
  itemfill(itemp,l);
 }
 return true;
}

void tableedit::itemfill(TE_ITEM*&dst,WPARAM count) const{
 memset(dst,0,count*sizeof(TE_ITEM));
 dst+=count;
}
void tableedit::itemmove(TE_ITEM*&dst,const TE_ITEM*&src,WPARAM count) const{
 memcpy(dst,src,count*sizeof(TE_ITEM));
 dst+=count;
 src+=count;
}

bool tableedit::insertcol(UINT ii, UINT mm) {
 tableaxis defcol={m.defXStyle,m.minW};
 cols.insert(&cols[ii],mm,defcol);
 TE_ITEM*newitems=new TE_ITEM[(WPARAM)cols.size()*rows.size()];
 if (!newitems) return false;
 TE_ITEM*newitemp=newitems;	// Ziel der Kopieraktion
 const TE_ITEM*olditemp=items;	// Quelle der Kopieraktion
 for (int j=0; j<(int)rows.size(); j++) {
  itemmove(newitemp,olditemp,ii);
  itemfill(newitemp,mm);	// Flache Kopie mit Einfügungen von Nullen
  itemmove(newitemp,olditemp,cols.size()-(ii+mm));
 }
 olditemp=items;
 items=newitems;
 delete[] olditemp;
 InvalidateRect(wnd,0,TRUE);
 return true;
}
bool tableedit::deletecol(UINT ii, UINT mm) {
 cols.erase(&cols[ii],&cols[ii+mm]);
 TE_ITEM*newitems=new TE_ITEM[(WPARAM)cols.size()*rows.size()];
 if (!newitems) return false;
 TE_ITEM*newitemp=newitems;	// Ziel der Kopieraktion
 const TE_ITEM*olditemp=items;	// Quelle der Kopieraktion
 for (int j=0; j<(int)rows.size(); j++) {
  itemmove(newitemp,olditemp,ii);	// Flache Kopie
  olditemp+=mm;
  itemmove(newitemp,olditemp,cols.size()-ii);
 }
 olditemp=items;
 items=newitems;
 delete[] olditemp;
 InvalidateRect(wnd,0,TRUE);
 return true;
}
bool tableedit::insertrow(UINT jj, UINT nn) {
 tableaxis defrow={m.defYStyle,m.minH};
 rows.insert(&rows[jj],nn,defrow);
 TE_ITEM*newitems=new TE_ITEM[(WPARAM)cols.size()*rows.size()];
 if (!newitems) return false;
 TE_ITEM*newitemp=newitems;
 const TE_ITEM*olditemp=items;
 itemmove(newitemp,olditemp,cols.size()*jj);
 itemfill(newitemp,cols.size()*nn);
 itemmove(newitemp,olditemp,cols.size()*(rows.size()-(jj+nn)));
 olditemp=items;
 items=newitems;
 delete[] olditemp;
 InvalidateRect(wnd,0,TRUE);
 return true;
}
bool tableedit::deleterow(UINT jj, UINT nn) {
 rows.erase(&rows[jj],&rows[jj+nn]);
 TE_ITEM*newitems=new TE_ITEM[(WPARAM)cols.size()*rows.size()];
 if (!newitems) return false;
 TE_ITEM*newitemp=newitems;
 const TE_ITEM*olditemp=items;
 itemmove(newitemp,olditemp,cols.size()*jj);
 olditemp+=cols.size()*nn;
 itemmove(newitemp,olditemp,cols.size()*(rows.size()-jj));
 olditemp=items;
 items=newitems;
 delete[] olditemp;
 InvalidateRect(wnd,0,TRUE);
 return true;
}
void tableedit::setfont(HFONT font) {
 deffont=font;
 if (boldfont) DeleteFont(boldfont);
 boldfont=0;
 if (font) {
  LOGFONT fnt;
  GetObject(font,sizeof fnt,&fnt);
  fnt.lfWeight=700;
  boldfont=CreateFontIndirect(&fnt);
 }
}
char tableedit::hittest(POINT&pt) {
// pt-Input: Mauskoordinaten
// pt-Output: Tabellenkoordinaten (0..(cols/rows).size())
// Wenn Ausgabewert == rows/cols.size, dann Gültigkeitsbit nicht gesetzt.
// Das wäre ein Klick in den Freiraum, um neue Zeile/Spalten zu adressieren.
// Zurzeit ohne Scrollbar!!
 int x=m.borW+m.mouE,y=m.borW+m.mouE;
 UINT i=(UINT)-1,j=(UINT)-1;
 char ret=0;
 while(++i<cols.size()) {
  x+=m.padX+cols[i].width+m.padX+m.borW;
  if (pt.x<x) {
   ret|=1;
   if (pt.x>=x-m.borW-m.mouE-m.mouE) ret|=4;	// Ost-West-Pfeil
   break;
  }
 }
 while(++j<rows.size()) {
  y+=m.padY+rows[j].width+m.padY+m.borW;
  if (pt.y<y) {
   ret|=2;
   if (pt.y>=y-m.borW-m.mouE-m.mouE) ret|=8;	// Nord-Süd-Pfeil
   break;
  }
 }
 pt.x=i;
 pt.y=j;
 return ret;
}
bool tableedit::okaytoedit(WPARAM ii) {
 UINT y=ii/cols.size();
 if (y>=rows.size()) return false;
 UINT x=ii%cols.size();
 if (cols[x].flags&TEAS_NOEDIT) return false;
 if (rows[y].flags&TEAS_NOEDIT) return false;
 return true;  
}
void tableedit::killedit(bool save) {	// "edit" muss gültig sein!
 if (save && SendMessage(edit,EM_GETMODIFY,0,0)) {
  TE_ITEM&item=items[editindex];
  TCHAR buf[256];
  GetWindowText(edit,buf,elemof(buf));
  if (item.s) delete[] item.s;
#ifdef UNICODE
  int l=WideCharToMultiByte(CP_UTF8,0,buf,-1,0,0,0,0);
  item.s=(char*)malloc(l);
  WideCharToMultiByte(CP_UTF8,0,buf,-1,item.s,l,0,0);
#else
  item.s=newstr(buf);
#endif
  SendMessage(GetParent(wnd),WM_COMMAND,MAKELONG(GetWindowID(wnd),EN_CHANGE),(LPARAM)wnd);
 }
 DestroyWindow(edit); edit=0;
}
void tableedit::setedit(WPARAM ii) {	// "edit" muss ungültig sein!
 while (!okaytoedit(ii)) ii++;
 editindex=ii;
 UINT y=ii/cols.size();
 UINT x=ii%cols.size();
 if (y>=rows.size()) return;
 Rect r;
 itemrect(x,y,r);
 TCHAR buf[256];
 buf[gettext(items[ii],buf,elemof(buf)-1)]=0;	// Hier wird's nullterminiert benötigt!
 DWORD sty=WS_VISIBLE|WS_CHILD|ES_AUTOHSCROLL|ES_AUTOVSCROLL|WS_BORDER|ES_WANTRETURN;
 char ali=getalign(x,y);
 switch (ali) {
  case 6: sty|=ES_CENTER; break;
  case 0: break;
  default: sty|=ES_RIGHT;
 }
 InflateRect(&r,-1,-1);
 edit=CreateWindowEx(0,TEXT("Edit"),buf,sty,
   r.left,r.top,r.width(),r.height(),
   wnd,0,0,0);
 orgeditproc=SubclassWindow(edit,editsubproc);
 SetWindowFont(edit,deffont,FALSE);
 Edit_SetSel(edit,0,-1);
 SetFocus(edit);
}
LRESULT tableedit::wndproc(UINT msg, WPARAM wParam, LPARAM lParam) {
 switch (msg) {
  case TE_INITSTORAGE: return initstorage(LOWORD(wParam),HIWORD(wParam));
  case TE_SETAXIS: {
   std::vector<tableaxis>*axis=HIWORD(wParam)?&rows:&cols;
   if (LOWORD(wParam)>=axis->size()) return FALSE;
   (axis->operator[](LOWORD(wParam))).flags=(int)lParam;
  }return TRUE;
  case TE_SETTEXT: {
   int i=itemindex(wParam); if (i<0) return FALSE;
   int l=strlen((char*)lParam)+1;
   char*p=(char*)malloc(l);
   if (!p) return FALSE;
   memcpy(p,(char*)lParam,l);
   delete[] items[i].s;
   items[i].s=p;
   InvalidateRect(wnd,NULL,TRUE);
  }return TRUE;
  case TE_GETTEXT: {
   int i=itemindex(wParam); if (i<0) return FALSE;
   strcpy((char*)lParam,items[i].s);
  }return TRUE;
  case TE_GETTEXTLEN: {
   int i=itemindex(wParam); if (i<0) return 0;
   return strlen(items[i].s)+1;	// hier: inklusive Null
  }
  case TE_GETTEXTPTR: {
   int i=itemindex(wParam); if (i<0) return 0;
   return (LRESULT)items[i].s;
  }
  case TE_SETTEXTPTR: {
   int i=itemindex(wParam); if (i<0) return 0;
   delete[] items[i].s;
   items[i].s=(char*)lParam;
   InvalidateRect(wnd,NULL,TRUE);
  }return 0;
  case TE_GETCOUNT: return MAKELONG(cols.size(),rows.size());
  case TE_HITTEST: return hittest(*(POINT*)lParam);
  case TE_INSERT: return HIWORD(wParam) ? insertrow(LOWORD(wParam)) : insertcol(LOWORD(wParam));
  case TE_DELETE: return HIWORD(wParam) ? deleterow(LOWORD(wParam)) : deletecol(LOWORD(wParam));
  case TE_GETITEM: {
   WPARAM i=itemindex(wParam);	// Zeiger in Item-Liste
   if (i==(WPARAM)-1) return 0;
   return LRESULT(items+i);
  }
  case TE_SETITEM: {
   WPARAM i=itemindex(wParam);	// Zeiger in Item-Liste
   if (i==(WPARAM)-1) return 0;
   TE_ITEM*lp=(TE_ITEM*)lParam;
   if (items[i].s) delete[] items[i].s;
   items[i]=*lp;		// mit Zeiger kopieren
   items[i].s=newstr(lp->s);
  }return TRUE;
  case TE_GETMETRICS: return LRESULT(&m);
  case TE_SETMETRICS: m=*(TE_METRICS*)lParam; return TRUE;
  case TE_INITHEADER: {
   m.defXStyle=TEAS_DECALIGN;	// für Zahlen vorbereiten
   initstorage((UINT)wParam,1);
   const char*p=(const char*)lParam;
   for (UINT i=0;i<(UINT)wParam;i++) {
    int l=strlen(p)+1;
    items[i].s=newcopy(p,l); p+=l;
   }
   m.yMin=1;			// Nicht reduzieren
   rows[0].flags=TEAS_HEADER|TEAS_STICKY|TEAS_NOEDIT|TEAS_SETALIGN|TEAS_LEFTALIGN;
   m.xMin=m.xMax=(UINT)wParam;	// Spalten fixieren
  }return TRUE;
  case TE_INITHEADERFOOTER: {
   m.defXStyle=TEAS_DECALIGN;	// für Zahlen vorbereiten
   initstorage((UINT)wParam,2);
   const char*p=(const char*)lParam;
   for (UINT i=0;i<(UINT)wParam;i++) {
    int l=strlen(p)+1;
    items[i].s=newcopy(p,l); p+=l;
    l=strlen(p)+1;
    items[(UINT)wParam+i].s=newcopy(p,l); p+=l;
   }
   m.yMin=2;			// Nicht reduzieren
   rows[1].flags=TEAS_HEADER|TEAS_STICKY|TEAS_NOEDIT|TEAS_SETALIGN|TEAS_RIGHTALIGN;
   rows[0].flags=TEAS_HEADER|TEAS_STICKY|TEAS_NOEDIT|TEAS_SETALIGN|TEAS_LEFTALIGN;
   m.xMin=m.xMax=(UINT)wParam;	// Spalten fixieren
  }return TRUE;
  case WM_SIZE: {
   onSize(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
  }return 0;
  case WM_PRINTCLIENT: {
   onPaint((HDC)wParam);
  }return 0;
  case WM_PAINT: {
   PAINTSTRUCT ps;
   BeginPaint(wnd,&ps);
   onPaint(ps.hdc);
   EndPaint(wnd,&ps);
  }return 0;
  case WM_RBUTTONDOWN:
  case WM_MBUTTONDOWN:
  case WM_LBUTTONDOWN:
  case WM_LBUTTONUP:
  case WM_MBUTTONUP:
  case WM_RBUTTONUP:
  case WM_MOUSEWHEEL:
  case WM_MOUSEMOVE: {
   POINT pos={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
   char hit=hittest(pos);
   switch (msg) {
    case WM_MOUSEMOVE: {
     BYTE cursor=0x88;	// IDC_NO;
     static const BYTE cursors[]={0x00,0x84,0x85,0x82};	// Offsets zu 0x7F00
     if ((hit&3)==3) cursor=cursors[hit>>2&3];
     SetCursor(LoadCursor(0,MAKEINTRESOURCE(0x7F00+cursor)));
    }break;
    case WM_LBUTTONDOWN: if (hit==3) {	// Treffer, nicht auf Rand
     WPARAM ii=itemindex(pos.x,pos.y);
     if (okaytoedit(ii)) {SetFocus(0); setedit(ii);}
    }break;
   }
  }break;
  case WM_SETFOCUS: setedit(editindex); break;
  case WM_COMMAND: switch (HIWORD(wParam)) {
   case EN_KILLFOCUS: killedit(); break;
   case EN_VSCROLL: {
    WPARAM nitems=(WPARAM)cols.size()*rows.size();
    WPARAM cur=editindex;
    do{
     switch (LOWORD(wParam)) {
      case VK_LEFT: cur=(cur?cur:nitems)-1; break;
      case VK_RIGHT: if (++cur==nitems) cur=0; break;
      case VK_UP: cur-=cols.size(); if ((INT_PTR)cur<0) cur+=nitems; break;
      case VK_DOWN: cur+=cols.size(); if (cur>=nitems) cur-=nitems; break;
     }
    }while (!okaytoedit(cur));
    SetFocus(0);
    setedit(cur);
   }break;
  }break;
  case WM_SETFONT: {
   setfont((HFONT)wParam);
   if (LOWORD(lParam)) InvalidateRect(wnd,0,TRUE);
  }return 0;
  case WM_CONTEXTMENU: {
   if ((HWND)wParam==wnd) {
    POINT pt={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)},pt2=pt;
    ScreenToClient(wnd,&pt);
    char hit=hittest(pt);
    if (!(hit&0xC0)) {	// Nicht auf Rand
     HMENU menu=LoadMenu(0,MAKEINTRESOURCE(12));
     if (cols.size()<=m.xMin
      || !(hit&1)
      || cols[pt.x].flags&(TEAS_NOEDIT|TEAS_STICKY))
      EnableMenuItem(menu,4,MF_GRAYED);
     if (cols.size()>=m.xMax 
      || !pt.x && cols[0].flags&TEAS_HEADER
      || pt.x && pt.x==cols.size() && cols[pt.x-1].flags&TEAS_HEADER)
      EnableMenuItem(menu,3,MF_GRAYED);
     if (rows.size()<=m.yMin
      || !(hit&2)
      || rows[pt.y].flags&(TEAS_NOEDIT|TEAS_STICKY))
      EnableMenuItem(menu,2,MF_GRAYED);
     if (rows.size()>=m.yMax
      || !pt.y && rows[0].flags&TEAS_HEADER
      || pt.y && pt.y==rows.size() && rows[pt.y-1].flags&TEAS_HEADER)
      EnableMenuItem(menu,1,MF_GRAYED);
     switch (TrackPopupMenu(GetSubMenu(menu,0),TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,pt2.x,pt2.y,0,wnd,0)) {
      case 1: insertrow(pt.y); break;
      case 2: deleterow(pt.y); break;
      case 3: insertcol(pt.x); break;
      case 4: deletecol(pt.x); break;
     }
     DestroyMenu(menu);
    }
   }
  }break;
 }
 return DefWindowProc(wnd,msg,wParam,lParam);
}

tableedit::tableedit(HWND hwnd) {
 wnd=hwnd;
 items=0;
 editindex=0;
 memset(&m,0,sizeof m);
 m.border=GetSysColor(COLOR_3DLIGHT);
 m.fgError=RGB(0,0,0);
 m.bgError=RGB(255,128,128);
 static const BYTE defMetrics[6]={/*borW*/1,/*padX*/5,/*padY*/3,/*minW*/8,/*minH*/12,/*mouE*/1};
 memcpy(&m.borW,defMetrics,sizeof defMetrics);
 m.yMax=m.xMax=(UINT)-1;
 m.DecimalChar=::DecimalChar;
 setfont((HFONT)SendMessage(GetParent(hwnd),WM_GETFONT,0,0));
}

tableedit::~tableedit() {
 initstorage(0,0);
 setfont(0);
}

void TableInit(void) {	// Klassen-Registrierung
 tableedit::init();
}
Detected encoding: UTF-80