Source file: /~heha/hs/Funkuhr.zip/src/DlgWetter.c

/********************************
 * Projekt: Funkuhr DCF77	*
 * Eigenschaftsseite äWetterô	*
 * Darstellung des Wettertrends	*
 * fⁿr eine Vorhersageregion	*
 ********************************/

#include "Funkuhr.h"

/************************************
 * Malfunktionen fⁿr Wettersymbolik *
 ************************************/
 
static struct {
 HBRUSH brSonne;	// Sonnenkreis
 HBRUSH brMond;		// Mond-Inneres
 HBRUSH brRegen;	// Regen-Striche
 HPEN   peSonne;	// Sonnenstrahlen
 HPEN   peWind[2];	// Rand fⁿr Wind, Index 0 = normaler Wind, 1 = Sonderwind
 HPEN   peWolke;
 HPEN   peFlockeH;	// Schneeflocke-Hintergrund (dunkel)
 HPEN   peFlockeV;	// Schneeflocke-Vordergrund (wei▀)
 HPEN   peTropfen;
 HFONT  fnr[4];		// Vier gedrehte, doppelt-gro▀e Fettschriften, je 45░
 HFONT	fnb;		// Normale Fettschrift
 HFONT	fnn;		// Schmale Schrift
}gdi;
static int gdiusage;
static SIZE b0size;	// Gr÷▀e von gdi.fnb, in Pixel; Breite einer Ziffer

static HPEN CreateGeoPen(int width, COLORREF color) {
 LOGBRUSH br={BS_SOLID,color};
 return ExtCreatePen(PS_GEOMETRIC|PS_SOLID|PS_ENDCAP_SQUARE|PS_JOIN_MITER,width,&br,0,NULL);
}

void CreateSymbGdiHandles() {
 if (!gdiusage) {
  LOGFONT lf;
  HDC dc;
  HFONT ofont;
  int i;
  gdi.brSonne  =CreateSolidBrush(RGB(255,255,0));
  gdi.brMond   =CreateSolidBrush(RGB(128,128,0));
  gdi.brRegen  =CreateSolidBrush(RGB(0,0,192));
  gdi.peSonne  =CreateGeoPen(1,RGB(192,192,0));
  gdi.peWind[0]=CreateGeoPen(4,RGB(128,128,128));
  gdi.peWind[1]=CreateGeoPen(4,RGB(128,128,255));
  gdi.peWolke  =CreateGeoPen(3,RGB(96,96,96));
  gdi.peFlockeH=CreatePen(PS_SOLID,5,RGB(128,128,128));
  gdi.peFlockeV=CreatePen(PS_SOLID,3,RGB(255,255,255));
  gdi.peTropfen=CreatePen(PS_SOLID,0,RGB(0,0,128));
  GetObject(GetStockFont(DEFAULT_GUI_FONT),sizeof(lf),&lf);
  lf.lfHeight=MulDiv(lf.lfHeight,12,11);	// sonst erscheint "Arial" im VerhΣltnis zu klein
  lf.lfWeight=FW_BOLD;
  lf.lfQuality=PROOF_QUALITY|ANTIALIASED_QUALITY;
  lstrcpyn(lf.lfFaceName,T("Arial Narrow"),elemof(lf.lfFaceName));
  gdi.fnn=CreateFontIndirect(&lf);
  lf.lfFaceName[5]=0;	// "Arial"
  gdi.fnb=CreateFontIndirect(&lf);
  dc=GetDC(0);
  ofont=SelectFont(dc,gdi.fnb);
  GetTextExtentPoint32W(dc,L"0",1,&b0size);
  SelectFont(dc,ofont);
  ReleaseDC(0,dc);
  lf.lfHeight<<=1;
  for (i=0; i<elemof(gdi.fnr); i++) {
   lf.lfEscapement=lf.lfOrientation=(i-1)*450;
   gdi.fnr[i]=CreateFontIndirect(&lf);
  }
 }
 gdiusage++;
}

void DeleteSymbGdiHandles() {
 if (!--gdiusage) {
  int i;
  for (i=0; i<sizeof(gdi)/sizeof(HGDIOBJ); i++) DeleteObject(((HGDIOBJ*)&gdi)[i]);
 }
}


// Auf die Spitze gestelltes Vielzack (n max. 16), n = Anzahl der Spitzen * 2
static void ptStern(HDC dc, int ra, int ri, int n) {
 POINT poly[16];
 int i;
 for (i=0; i<n; i++) {
  poly[i]=sincos(i&1?ri:ra,i*3600/n);
 }
 Polygon(dc,poly,n);
}

static int Mond[4];	// 4x Mondalter (heute, morgen, ⁿbermorgen, Tag4) in 0,1-░-Einheiten, jeweils zu Mittag (12:00)
void CalcMondalter(void) {
 SYSTEMTIME st;
 U64 ft;
 int i;
 double JD2000;		// Julianisches Datum bzgl. 2000/01/01 12:00 MEZ / MESZ
 GetLocalTime(&st);
 st.wHour=12, st.wMinute=0;
 SystemTimeToFileTime(&st,&ft.ft);
 ft.ull>>=9;
 ft.ull/=1171875;	// Ergebnis in Minuten seit 1601/01/01 00:00 Lokalzeit, passt in ein DWORD
 ft.Lo+=geotz;		// UTC ermitteln
 JD2000=(double)ft.ll/(24*60)-94187-51544.5;
 for (i=0; i<elemof(Mond); i++) {
  Mond[i]=lrint(Mondalter(JD2000)*10);
  JD2000++;		// ganzer Tag vorwΣrts; TODO: Sommer/Winterzeitumschaltung einrechnen (23h, 25h)
 }
}

// 1 4-zackiger Stern
static void mdrStern(HDC dc, int x, int y, int r) {
 SetWindowOrgEx(dc,-x,-y,NULL);
 ptStern(dc,r,r/2,8);
}

// Zeichnet Mond und 3 Sterne
// Mondphase = 0..3600, 0 = Neumond, 1800 = Vollmond
// TODO: Vielleicht doch wieder Sinus nehmen
// Vermeiden von Regionskuddelmuddel (nicht hochskalierbar) durch Spline-NΣherung
static void ptMond(HDC dc, int yoff, int Mondphase) {
 SetWindowOrgEx(dc,16,16-yoff,NULL);
//w=abs(sinus(14,Mondphase+900));
//p=Mondphase/900+1;
//if (w!=14)
 Mondphase=MulDiv(Mondphase,28,3600);
 if (Mondphase && Mondphase!=14) {
  HRGN rgn0,rgn1;
  BYTE p=Mondphase/7+1;	// Phase (1,2,3,4 = erstes Viertel usw.)
  int w=(Mondphase+7)%28;	// Breite der inneren Ellipse {Schmu: kein Sinus fⁿr bessere Optik}
  if (w>=14) w=28-w;	// sondern eine Dreieckfunktion
  if (w>=7) w=14-w;
  w<<=1;
  rgn1=CreateEllipticRgn(-w+1,-14,w+1,16);
  w=p&1?-16:0;		// Rechteck links oder rechts
  rgn0=CreateRectRgn(w,-16,w+16,17);
  CombineRgn(rgn1,rgn1,rgn0,RGN_OR);
  DeleteObject(rgn0);
  rgn0=CreateEllipticRgn(-15,-15,17,17);
  CombineRgn(rgn0,rgn0,rgn1,p&2?RGN_AND:RGN_DIFF);
  DeleteObject(rgn1);
  FillRgn(dc,rgn0,gdi.brSonne);
  DeleteObject(rgn0);
 }
 SelectBrush(dc,Mondphase==14?gdi.brSonne:GetStockBrush(NULL_BRUSH));
 SelectPen(dc,gdi.peSonne);	// Rand
 Ellipse(dc,-16,-16,16,16);
 SelectBrush(dc,gdi.brSonne);
 mdrStern(dc,22,-22+yoff,8);
 if (Mondphase!=14) mdrStern(dc,7,-26+yoff,6);	// Bei Vollmond 1 Stern weniger
 mdrStern(dc,24,-6+yoff,6);
 if (Mondphase==0) mdrStern(dc,6,-6+yoff,6);	// Bei Neumond 1 Stern mehr
}

// r = Gesamt-Radius mit Strahlen
static void mdrSonne(HDC dc, int x, int y, int r) {
 int rk=MulDiv(r,3,5);	// Kreis-Radius
 int rs=rk+2;	// Strahlen-Innenradius
 int i;
 SelectBrush(dc,gdi.brSonne);
 SelectPen(dc,gdi.peSonne);
 SetWindowOrgEx(dc,-x,-y,NULL);
 for (i=0; i<3600; i+=300) {	// 12 Sonnenstrahlen als spitze Dreiecke
  POINT poly[3];
  poly[0]=sincos(r,i);		// Spitze
  poly[1]=sincos(rs,i-120);	// Fu▀ "links"
  poly[2]=sincos(rs,i+120);	// Fu▀ "rechts"
  Polygon(dc,poly,elemof(poly));	// Sonnenstrahlen zuerst
 }
 Ellipse(dc,-rk,-rk,rk,rk);
}

static void ptSonne(HDC dc, BYTE kode) {
 if (kode==1) mdrSonne(dc,0,0,30);	// mittig
 else mdrSonne(dc,12,-12,20);		// rechts oben
}

// Byte-Array zu Punkten, symmetrisch zur Y-Achse
void bloatsym(POINT*d, const char*s, int l) {
 int i;
 for (i=0;i+i<l;i++) {
  d[l-1-i].x=-(d[i].x=s[i+i]);	// spiegeln an X-Achse
  d[l-1-i].y=d[i].y=s[i+i+1];	// Y-Werte belassen
 }
}

static void SetVertex(TRIVERTEX *v, int x, int y, COLORREF color) {
 v->x=x;
 v->y=y;
 v->Red  =GetRValue(color)<<8;
 v->Green=GetGValue(color)<<8;
 v->Blue =GetBValue(color)<<8;
 v->Alpha=(BYTE)(color>>24)<<8;
}

// v = Werte, a = AbstΣnde
static int Balance(int v1, int a1, int v2, int a2) {
 return v1+MulDiv(v2-v1,a1,a1+a2);
}

static void BalanceVertex(TRIVERTEX *d, const TRIVERTEX *s1, int a1, const TRIVERTEX *s2, int a2) {
 int i;
 d->x=s1->x;	// Trick!! Nimm X vom ersten und Y vom zweiten
 d->y=s2->y;
 for (i=0; i<4; i++) (&d->Red)[i]=Balance((&s1->Red)[i],a1,(&s2->Red)[i],a2);
}

// Rechteck aus zwei Dreiecken zusammensetzen und mit Farbverlauf fⁿllen, fⁿr Wolkendarstellung
// c1, c2 = Start- und Endfarbe, angle in Zehntelgrad
// Das High-Byte von COLORREF enthΣlt Transparenz (funktioniert aber nicht so einfach)
static void GradientFillRect(HDC dc, const RECT *r, COLORREF c1, COLORREF c2, int angle) {
 TRIVERTEX tv[4];
 static const char Folge[]={0,1,2,0,1,3};
 int punkte[6];
 int mode=GRADIENT_FILL_TRIANGLE;
 div_t quad;
 quad=udiv(angle,900);		// quad.quot = Quadrant (Bits 1:0 = 0..3)
 SetVertex(tv+0,r->left,r->top,quad.quot&2?c2:c1);
 SetVertex(tv+1,r->right,r->bottom,quad.quot&2?c1:c2);
 if (quad.rem) {		// SchrΣges Fⁿllen?
  POINT w=sincos(r->right-r->left,quad.rem);	// x = cos, y = sin
  POINT h=sincos(r->bottom-r->top,quad.rem);
  if (quad.quot&1) {		// Quadrant II (nach links-unten) oder IV?
   tv[0].x=r->right;		// rechts statt links oben 
   tv[1].x=r->left;		// links statt rechts unten
   BalanceVertex(tv+2,tv+0,h.x,tv+1,w.y);	// rechts unten
   BalanceVertex(tv+3,tv+1,h.x,tv+0,w.y);	// links oben
  }else{
   BalanceVertex(tv+2,tv+0,h.y,tv+1,w.x);	// links unten
   BalanceVertex(tv+3,tv+1,h.y,tv+0,w.x);	// rechts oben
  }
 }else mode=quad.quot&1?GRADIENT_FILL_RECT_V:GRADIENT_FILL_RECT_H;	// Optimiert das der Compiler?? (x&1?1:0 => x&1)
 bloat(punkte,Folge,6);
 GradientFill(dc,tv,mode==GRADIENT_FILL_TRIANGLE?4:2,punkte,mode==GRADIENT_FILL_TRIANGLE?2:1,mode);
}

// Hilfsfunktion, malt 3 B÷gen (Vorlage: mdr)
// Der Nullpunkt ist an der Wolke unten mittig, die Wolke ist ca. 50 Pel breit
static void mdrWolkePfad(HDC dc) {
 static const char k[]={-14,0,-30,0,-30,-18,-14,-18,-14,-30};		// linke HΣlfte der Wolke
 POINT kp[10];
 bloatsym(kp,k,10);
 PolyBezier(dc,kp,10);
}

// <hell> = Helligkeit links unten (etwa 128..191)
// <offen> = Umrandung an Unterseite nicht durchgezogen
static void mdrWolke(HDC dc, int x, int y, BYTE hell, bool offen) {
 RECT r;
 SetWindowOrgEx(dc,-x,-y,NULL);
 BeginPath(dc);
 mdrWolkePfad(dc);
 CloseFigure(dc);
 EndPath(dc);
 SelectClipPath(dc,RGN_AND);
 SetRect(&r,-32,-25,32,0);
 GradientFillRect(dc,&r,RGB(255,255,255),RGB(hell,hell,hell),1350);
 SelectClipRgn(dc,0);
 BeginPath(dc);
 mdrWolkePfad(dc);
 if (!offen) CloseFigure(dc);	// sonst unten offen lassen; da fΣllt 'was raus (Regen nΣmlich)
 EndPath(dc);
 SelectPen(dc,gdi.peWolke);
 StrokePath(dc);
}

static void myNebel(HDC dc, int y, BYTE hell) {
 RECT r;
 COLORREF c1,c2;
 c2=RGB(hell,hell,hell);
 hell-=64;
 c1=RGB(hell,hell,hell);
 SetRect(&r,0,0,54,4);
 for(;y<12;y+=8) {
  SetWindowOrgEx(dc,27,-y,NULL);
  GradientFillRect(dc,&r,c1,c2,0);
 }
}

// Die Niederschlagswolke ist stets an Basis-Position x=4 / y=12
static void ptWolke(HDC dc,BYTE kode) {
 int x,y;
 if ((kode&6)==4) {
  myNebel(dc,kode&1?-12:-22,(BYTE)(kode&0x80?192:255));
 }else{
  if (kode&4) {	// Zwei Wolken (dunkel)
   y=kode&1?8:-2;	// Wolken-Basis in AbhΣngigkeit von der Sonne
   mdrWolke(dc,-4,y,50,false);
   x=4;
   y=kode&0xF0 && !(kode&1)?8:18;	// Platz fⁿr Niederschlag machen
   if (kode&0x70) y=12;		// Platz fⁿr Schnee sichern
   mdrWolke(dc,x,y,50,kode&0x70);
  }else{	// Eine Wolke (hell)
   x=-4;
   y=kode&0xF0?kode&1?12:2:kode&1?22:12;
   if (kode&0x70) y=12;		// Platz fⁿr Schnee sichern
   mdrWolke(dc,x,y,165,kode&0x70);
  }
 }
}

// Einen Regen-Strahl zeichnen
static void mdrRegen(HDC dc, int x, int w) {
 const char Strahl[]={0,0, 0,0, -6,24, -6,24};
 POINT poly[4];
 bloat((int*)poly,Strahl,8);
 poly[1].x+=w;
 poly[2].x+=w;
 SetWindowOrgEx(dc,-x,-5,NULL);
 Polygon(dc,poly,elemof(poly));
}

static void ptRegen(HDC dc, BYTE kode) {
 BYTE m,p=0xA;
 char x,w=5;	// Standard-Regen: 2 Striche
 SelectBrush(dc,gdi.brRegen);
 SelectPen(dc,GetStockPen(NULL_PEN));
 if ((kode&0x30)==0x10) {p=0x4; w=3;}	// 00100 = Niesel
 if (kode&0x80) p=0x15;		// Schweres Wetter: 3 Striche
 for (m=0x10, x=-5; m; m>>=1, x+=4) {
  if (m&p) mdrRegen(dc,x,w);
 }
}

static void ptKristall(HDC dc,int x) {
 POINT Ends[6];
 int i;
 SetWindowOrgEx(dc,-x,-23,NULL);
 SelectPen(dc,gdi.peFlockeH);
 for (i=0; i<6; i+=2) {
  Ends[i].x=sinus(7,i*10+5);
  Ends[i].y=sinus(7,i*10+20);
  Ends[i+1].x=-Ends[i].x;
  Ends[i+1].y=-Ends[i].y;
  Polyline(dc,Ends+i,2);
 }
 SelectPen(dc,gdi.peFlockeV);
 for (i=0; i<6; i+=2) Polyline(dc,Ends+i,2);
}

static void ptFlocke(HDC dc, BYTE kode) {
 char x;
 BYTE m;
 BYTE p=0xA;			//01010 = 2 Flocken in der Mitte
 if (kode&0x30) {		// mit Regen vermischt?
  p=0x10;			//10000 = 1 Flocke links
  if (kode&0x80) p=0x11;	//10001 = 2 Flocken links und rechts
 }else if (kode&0x80) p=0x15;	//10101 = 3 Flocken (ohne Regen)
// if (kode&8 && !(p&=~0x10)) p=1;//Gewitter? (Nur zum Test!)
 for (m=0x10, x=-40; m; m>>=1, x+=21) {
  if (p&m) ptKristall(dc,x>>1);
 }
}

static void ptBlitz(HDC dc) {
 static const char Blitz[]={-18,-2, -25,18, -20,16, -24,25, -28,22, -27,32, -17,29, -22,26, -12,11, -20,13, -8,-5};
 POINT poly[elemof(Blitz)/2];
 bloat((int*)poly,Blitz,elemof(Blitz));
 SetWindowOrgEx(dc,0,0,NULL);
 SelectBrush(dc,gdi.brSonne);
 SelectPen(dc,gdi.peSonne);
 Polygon(dc,poly,elemof(poly));
}

static void ptAchtung(HDC dc) {
 SetWindowOrgEx(dc,0,0,NULL);
 DrawIcon(dc,-32,-32,LoadIcon(0,IDI_WARNING));
}

static void ptTropfenPfad(HDC dc) {
 static const char Tropfen[]={0,-38,-4,-18, -22,-18,-22,0,-22,14, -14,22,0,22};
 POINT poly[13];
 bloatsym(poly,Tropfen,13);
 PolyBezier(dc,poly,13);
}

// Niederschlagswahrscheinlichkeit anzeigen, Gr÷▀e 60x52 pel
static void ptTropfen(HDC dc, int val) {
 RECT r;
 WCHAR s[4];
 SetWindowOrgEx(dc,0,0,NULL);
 SelectFont(dc,gdi.fnr[1]);	// horizontal
 BeginPath(dc);
 ptTropfenPfad(dc);
// CloseFigure(dc);
 EndPath(dc);
 SelectClipPath(dc,RGN_AND);
 SetRect(&r,-38,-38,22,22);
 GradientFillRect(dc,&r,RGB(127,127,255),RGB(0,0,128),1350);
 SelectClipRgn(dc,0);
 SelectPen(dc,gdi.peTropfen);
 ptTropfenPfad(dc);
 SetTextAlign(dc,TA_CENTER|TA_BASELINE);
 SetTextColor(dc,RGB(255,255,255));
 TextOutW(dc,1,10,s,wnsprintfW(s,elemof(s),L"%d",val));
}

static int getNSW(DWORD vt) {
 int n=(vt>>12&7)*15;
 if (n>100) n=100;
 return n;
}

// Niederschlagswahrscheinlichkeit
void ptNSW(HDC dc, U64 v) {
 ptTropfen(dc,getNSW(v.Tag));
}

// Tabelle der WindstΣrken
// High-Nibble = 1. Zahl
// Low-Nibble = 2. Zahl, keine zweite Zahl (= 0), >=-Angabe (=F)
static const BYTE beaufort[8]={0x00,0x02,0x34,0x56,0x70,0x80,0x90,0xAF};

static void ptRose(HDC dc) {
 SetWindowOrgEx(dc,0,0,NULL);
 SelectBrush(dc,GetStockBrush(DKGRAY_BRUSH));
 SelectPen(dc,GetStockPen(NULL_PEN));
 ptStern(dc,40,30,16);
}

// <kode>
// Bit 2:0 = Windrichtung
// Bit 3 = Sonder-Wind
// Bit 6:4 = WindstΣrke
// Bit 7   = Bits 0..3 ungⁿltig
void ptWind(HDC dc, U64 v) {
 BYTE kode=(BYTE)(v.Nacht>>8);
 WCHAR s[4];
 PCWSTR t;
 POINT p1,p2;
 BYTE ws=kode>>4&7;	// WindstΣrke
 int dir45,dir;		// Windrichtung (dir45: 0=Nord, dir: Ost = 0░, Sⁿd = 90░ usw.)
 dir45=kode&7;
 if (kode&8) dir45=0640146>>(kode&7)*3;	// Windrichtung aus Sonderwind bestimmen
 dir=(dir45+6)*450;	// Zehntelgrad 
 SetWindowOrgEx(dc,0,0,NULL);
 SelectPen(dc,gdi.peWind[kode&0x88?1:0]);	// blaue Umrandung wenn undefinierte Winrichtung oder Sonder-Wind
 SelectBrush(dc,GetStockBrush(WHITE_BRUSH));
 if (kode&0x70) {
  if (kode&0x80) Rectangle(dc,-30,-30,30,30);	// undefinierte Windrichtung
  else if ((kode&0xF)==0x8) ptStern(dc,30,20,12);	// wechselnde Richtung
  else{
   p1=sincos(36,dir);
   OffsetWindowOrgEx(dc,p1.x,p1.y,NULL);
   p1=sincos(72,dir+200+ws*20);
   p2=sincos(72,dir-200-ws*20);
   Pie(dc,-72,-72,72,72,p1.x,p1.y,p2.x,p2.y);
  }
 }else{
  Ellipse(dc,-20,-20,20,20);	// Windstille
 }
 ws=beaufort[ws];
 t=L"%d-%d";
 if (!(ws&0x0F)) t+=3;
 if (!(~ws&0x0F)) t=L"\x2265%d";
 SelectFont(dc,gdi.fnr[3-dir45&3]);
 SetTextColor(dc,0);
 SetTextAlign(dc,TA_CENTER|TA_BASELINE);
 p1=sincos(48,dir);
 p2=sincos(8,(dir45&3)*450);
 p1.x+=p2.x;
 p1.y+=p2.y;
 TextOutW(dc,p1.x,p1.y,s,wnsprintfW(s,elemof(s),t,ws>>4,ws&0xF));
}

static void meWind(DWORD v, RECT*r) {
 BYTE kode=(BYTE)(v>>8);
 POINT p[4];
 BYTE ws=kode>>4&7;	// WindstΣrke
 int dir45,dir;		// Windrichtung (dir45: 0=Nord, dir: Ost = 0░, Sⁿd = 90░ usw.)
 dir45=kode&7;
 if (kode&8) dir45=0640146>>(kode&7)*3;	// Windrichtung aus Sonderwind bestimmen
 dir=(dir45+6)*450;	// Zehntelgrad 
 if (kode&0x70) {
  if (kode&0x80		// undefinierte Windrichtung
  || (kode&0xF)==0x8) SetRect(r,-30,-30,30,30);	// wechselnde Richtung
  else{
   HRGN rgn;
   p[0]=sincos(36,dir);
   p[1]=sincos(72,dir+200+ws*20);
   p[1].x+=p[0].x;
   p[1].y+=p[0].y;
   p[2]=sincos(72,dir-200-ws*20);
   p[2].x+=p[0].x;
   p[2].y+=p[0].y;
   p[3].x=-p[0].x;
   p[3].y=-p[0].y;
   rgn=CreatePolygonRgn(p,4,ALTERNATE);
   GetRgnBox(rgn,r);
   DeleteRgn(rgn);
  }
 }else{
  SetRect(r,-20,-20,20,20);	// Windstille
 }
 InflateRect(r,2,2);	// halbe Stiftbreite
}

static void ptSymb(HDC dc, DWORD kode) {
//				0    1    2    3    4    5    6    7    8    9   10   11   12   13   14   15
//static const BYTE Bits[16]={0xFF,0x01,0x03,0x07,0x06,0x04,0x05,0x27,0x16,0x36,0x3E,0x0F,0x57,0x47,0x66,0x46};

 static const BYTE Bits[16]={0xFF,0x01,0x03,0x07,0x06,0x0F,0x36,0x46,0x05,0x66,0x27,0x16,0x47,0x3E,0x04,0x57};
// Bit 0 = Sonne/Mond/Sterne
// Bit 1 = Helle Wolke
// Bit 2 = Dunkle Wolke (zusammen mit Helle Wolke) / Nebel
// Bit 3 = Blitz
// Bit 4 = Nieselregen
// Bit 5 = Regen
// Bit 6 = Schnee
// Bit 7 = Schweres Wetter
// Bit 11:8 = Achtungs-Symbol (sonstiges spezielles Wetter, Anzeige in Textform)
// Bit 26:24 = Tag+1 fⁿr Mond, sonst 0 = Sonne zeichnen
 kode=kode&0xFFFFFF80|Bits[kode&0x0F];
 if (kode&1) if (kode>>24) ptMond(dc,(BYTE)kode==0x01?10:0,Mond[(kode>>24)-1]); else ptSonne(dc,(BYTE)kode);
 if (kode&6) ptWolke(dc,(BYTE)kode);
 if (kode&8) ptBlitz(dc);
 if (kode&0x30) ptRegen(dc,(BYTE)kode);
 if (kode&0x40) ptFlocke(dc,(BYTE)kode);
 if (kode&0xF80) ptAchtung(dc);
}

static const BYTE Schwerwetter[16]={2,2,1,1,1,1,3,4,5,4,6,1,4,7,1,4};
static const BYTE Unwetter[16]={0x00,0x07,0x05,0x06,0x83,0x81,0x82,0x91,0x92,0xA1,0xA9,0xA2,0xB3,0xC3,0xD3,0xE3};

static DWORD TakeBest(U64 v, bool PreferNight) {
 if (!v.Nacht) PreferNight=false;
 else if (!v.Tag) PreferNight=true;
 else if (v.Nacht>>24<v.Tag>>24) PreferNight=true;
 else if (v.Nacht>>24>v.Tag>>24) PreferNight=false;
 return PreferNight?v.Nacht:v.Tag;
}

// Zeichnet ein Tagwetter-Symbol in ein nullzentriertes Quadrat (KantenlΣnge 64)
void ptSymbolTag(HDC dc, U64 v) {
 DWORD kode=TakeBest(v,false)&0xF;
 BYTE u;	// Unwetter-Kode
 if (v.Tag && !(v.Tag&1<<15) && (u=Unwetter[v.Tag>>8&0xF])&1) {
  kode|=u&0xF0?kode<<4:0x80;	// Schweres Wetter (zusΣtzliche Schneeflocken usw.) oder nur Achtung
 }
 ptSymb(dc,kode);
}

// Zeichnet ein Nachtwetter-Symbol in ein nullzentriertes Quadrat (KantenlΣnge 64)
void ptSymbolNacht(HDC dc, U64 v, int TagNr) {
 DWORD kode=TakeBest(v,true)>>4&0xF;
 BYTE u;	// Unwetter-Kode
 if (v.Tag && !(v.Tag&1<<15) && (u=Unwetter[v.Tag>>8&0xF])&2) {
  kode|=u&0xF0?kode<<4:0x80;
 }
 kode|=(DWORD)(TagNr+1)<<24;
 ptSymb(dc,kode);
}

// wie wnsprintfW(buf,"%d",Temperatur), aber "< -21 ░C" und "> 40 ░C" beachtend
static int MkTempStringW(DWORD v, PCWSTR fmt, PWSTR buf, int len) {
 PCWSTR q;
 int t;
 t=(v>>16&0x3F)-22;			// Temperatur in ░C
 q=L"";
 if (t==-22) {t++; q=L"< ";}
 if (t==41) {t--; q=L"> ";}
 return wnsprintfW(buf,len,fmt,q,t);
}

static void ptTemp(HDC dc, DWORD v) {
 WCHAR s[8];
 SIZE sz;
 SetTextColor(dc,RGB(255,255,255));
 SetBkMode(dc,OPAQUE);
 SetTextAlign(dc,TA_CENTER|TA_TOP);
 SetWindowOrgEx(dc,0,0,NULL);
 SelectFont(dc,gdi.fnr[1]);
 GetTextExtentPoint32W(dc,L"0",1,&sz);
 TextOutW(dc,0,-(sz.cy>>1),s,MkTempStringW(v,L" %s%d ",s,elemof(s)));
}

// Rechteck um ein Zentrum herum erzeugen
void SetRectC(RECT*r, int cx, int cy, int ex, int ey) {
 r->right =(r->left=cx-(ex>>1))+ex;
 r->bottom=(r->top =cy-(ey>>1))+ey;
}

static void meTemp(HDC dc, DWORD v, RECT*r) {
 SIZE sz;
 WCHAR s[8];
 SelectFont(dc,gdi.fnr[1]);
 GetTextExtentPoint32W(dc,s,MkTempStringW(v,L" %s%d ",s,elemof(s)),&sz);
 sz.cx+=1;	// Korrektur: Luft machen, sonst bleiben Reste stehen
 SetRectC(r,0,0,sz.cx,sz.cy);
}

void ptTempTag(HDC dc, U64 v) {
 SetBkColor(dc,RGB(128,0,0));
 ptTemp(dc,v.Tag);
}
void ptTempNacht(HDC dc, U64 v) {
 SetBkColor(dc,RGB(0,0,128));
 ptTemp(dc,v.Nacht);
}
bool TagNachtWarnung(U64 v) {
 return !(v.Tag&1<<15) && v.Tag&0xF00 || v.Nacht&1<<15;
}
void ptWarnung(HDC dc, U64 v) {
 if (TagNachtWarnung(v)) {
  SetWindowOrgEx(dc,0,0,NULL);
  DrawIcon(dc,-16,-16,LoadIcon(0,IDI_WARNING));// hier: mittig
 }
}

void Measure(HDC dc, U64 v, BYTE typ, RECT*r) {
 SetRectEmpty(r);
 switch (typ) {
  case 2: if (v.Tag) meTemp(dc,v.Tag,r); break;
  case 4: if (v.Nacht) meTemp(dc,v.Nacht,r); break;
  case 1:
  case 3: if (v.ll) SetRect(r,-32,-32,32,32); break;
  case 5: if (v.Tag) SetRect(r,-22,-38,23,23); break;
  case 6: if (v.Nacht) meWind(v.Nacht,r); break;
  case 7: if (TagNachtWarnung(v)) SetRect(r,-16,-16,16,16); break;
  default: return;
 }
 LPtoDP(dc,(POINT*)r,2);
}

/****************************
 * Die Cache-Interpretation *
 ****************************
Beispielsweise: MJD 04 (Localzeit)
Cache:	| Tag+0	| Tag+1	| Tag+2 | Tag+3	| hier werden die Regionen 0..59 betrachtet:

jetzt:04| 04	| 04	| 04	| 04	| ⁿblich nach fehlerfreiem Empfang
->Tag:	| heute	|morgen	| Tag3	| Tag4	| 22:59:15 Uhr MESZ
->Alter:| 00	| 00	| 00	| 00	|

jetzt:04| 05	| 04	| 04	| 04	| MEZ beginnend ab 23:02 Uhr
->Tag:	| morgen|morgen	| Tag3	| Tag4	| Die Info fⁿr "heute" ist weg
->Alter:| FF	| 00	| 00	| 00	| (irgendwie Murks)

jetzt:05| 05	| 05	| 05	| 04	| MESZ nach 17:59
->Tag	| heute	|morgen	| Tag3	| Tag3	| Die Info mit der h÷heren MJD gewinnt
->Alter:| 00	| 00	| 00	| 01	| (irgendwie Murks)

jetzt:07| 05	| 05	| 05	| 04	| Start 2 Tage spΣter
->Tag	| --	|gestern| heute	| heute	|
->Alter:| 02	| 02	| 02	| 03	| 02 = 48h, 03 = 72h
 */

int GetForecast(int region, U64 v[4]) {
 int j3m=0;
 int index;		// 0..479 -> Cache24
 int i,idx;		// 7..0 -> Vorhersage-Einheiten
 int ret=0;
 int age;
 DWORD*wetter=(DWORD*)v;
 __stosd(wetter,0,8);
 if ((unsigned)region>=90) region=Config.uRegion;
 if ((unsigned)region>=90) return 0;	//Fehler!
 for (i=7; --i>=0;) {
  if (region>=60) {
   if (i!=2 && i!=4) continue;	// Daten nur fⁿr "2" und "4"
   index=420+region-60+15*(i-2);
  }else{
   index=region+60*i;
  }
  age=GetAge(index,&j3m);	// Zeitstempel des Cache-Eintrags
  if (age>=0) {
   int korr=age-40-geotz/3;
   div_t d=udiv(korr,480);
   idx=i-(d.quot<<1);		// "zurⁿckschieben"
   if ((unsigned)idx>=8) continue;	// "von gestern": ignorieren
   age/=20;
   wetter[idx]=Cache24[index]&0xFFFFFF | age<<24;	// oberes Byte gibt Alter in Stunden an
   ret++;
  }
 }
 return ret;
}

// Temperaturangabe mit ░C
static int TempDegW(DWORD v, WCHAR*s, int len) {
 WCHAR t[16];
 MyLoadStringW(57,t,elemof(t));
 return MkTempStringW(v,t,s,len);
}

// Gemeinsames Vorhersage-Nibble generieren
static BYTE gemVN(U64 v, bool nacht) {
 BYTE vv=(BYTE)(v.Tag?v.Tag:v.Nacht);	// Gemeinsame (redundante) Vorhersage
 if (nacht) vv>>=4;			// High-Nibble fⁿr die Nacht nehmen, sonst Low-Nibble
 return vv&15;
}

// gemeinsam fⁿr ToolTipText auf Karte und Dialog
// Mit vorhergehendem '\n' wenn <statusbar> = false
// Alterntiver Text wenn <statusbar> = true
int SchwerwetterText(PTSTR s, int slen, bool nacht, U64 v, bool statusbar) {
 BYTE b=gemVN(v,nacht),c;
 TCHAR w[256],tn[32],*t=w;
 if (v.Tag && !(v.Tag&1<<15) && (c=Unwetter[v.Tag>>8&0xF])&(nacht+1)) {
  const TCHAR *q;	// Zusatz
  int l=0;
  LoadString(ghInstance,52,w,elemof(w));	// "Schweres Wetter: %s\0..."
  if (statusbar) {
   LoadString(ghInstance,51,tn,elemof(tn));	// "Tag: %s\0Nacht: %s\0..."
   t=GetStr(tn,nacht);
  }
  if (c&0xF0) {	//"B÷en", "Hochwasser" u.Σ.
   b=(c>>4);
   if (b==10) {	// "Eisregen"
    q=GetStr(w,15+(c>>3&1));	// " vormittags" / " nachmittags"
    lstrcat(t,T("%s"));		// unsauber wegen Pufferⁿberlaufsm÷glichkeit
   }
  }else b=Schwerwetter[b];	// "Schweres Wetter"
  if (!statusbar && slen>1) s[l++]='\n';
  return wnsprintf(s+l,slen-l,t,GetStr(w,b),q)+1;
 }
 return 0;
}

// ToolTipText (mehrzeilig) fⁿr Wettersymbol erzeugen
static int GenWSymText(PTSTR s, int slen, bool nacht, U64 v) {
 BYTE b=gemVN(v,nacht);
 int l;
 TCHAR t[320];			// Ressourcen-String
 LoadString(ghInstance,51,t,elemof(t));
 if (!nacht && b==1) b=0;	// am Tag "sonnig", nachts "klar"
 l=wnsprintf(s,slen,GetStr(t,nacht),GetStr(t,b+2));
 l+=SchwerwetterText(s+l,slen-l,nacht,v,false);
 if (!nacht && v.Tag&1<<15) {
  static const BYTE Sonnenschein[4]={0x02,0x24,0x56,0x78};	// Sonnenschein-Kode
  BYTE Sonne=Sonnenschein[v.Tag>>10&3];
  LoadString(ghInstance,55,t,elemof(t));		//"%d-%d Stunden"
  if (l && slen>l+1) s[l++]='\n';
  l+=wnsprintf(s+l,slen-l,t,Sonne>>4,Sonne&0x0F);

  LoadString(ghInstance,54,t,elemof(t));
  if (l && slen>l+1) s[l++]='\n';
  l+=wnsprintf(s+l,slen-l,t,GetStr(t,1+(v.Tag>>8&3)));
 }
 return l;
}

static int GenWindText(PTSTR s, int slen, DWORD vn) {
 int n;
 int l=0;
 int j,k;
 BYTE bft;
 TCHAR ws[5];		// WindstΣrke (numerisch)
 PCTSTR ct;		// Template
 TCHAR t[256];		// Ressourcen-String
 static const char wg[]={0,2,0,11,12,28,29,49,50,61,62,74,75,88,89,-1};	//Windgeschwindigkeits-Paare in km/h
 if (vn>>12&7 && !(vn&1<<15)) {	// WindstΣrke > 0 UND keine Anomalie
  LoadString(ghInstance,58,t,elemof(t));	// "Windrichtung: %s" und 8 Richtungen, 8 Sonder-Winde
  l+=wnsprintf(s+l,slen-l,t,GetStr(t,(vn>>8&0x0F)+1));
 }
 n=vn>>12&7;
 bft=beaufort[n];
 ct=T("%d-%d");
 if (!(bft&0x0F)) ct+=3;
 if (!(~bft&0x0F)) ct=
#ifdef UNICODE
		    L"\x2265%d";	// Der String wird einem Fenster ⁿbergeben
#else
		    ">=%d";		// TextOutW()-Ausgabe ist extra
#endif
 wnsprintf(ws,elemof(ws),ct,bft>>4,bft&15);
 LoadString(ghInstance,59,t,elemof(t));	// "WindstΣrke: %s bft (%d-%d %s/h) = %s"
 if (l && slen>l+1) s[l++]='\n';
 j=wg[2*n];	// Windgeschwindigkeit in km/h
 k=wg[2*n+1];
 if (iMeasure) {	// z÷llig, also Meilen ausspucken
  j=MulDiv(j,10,17);	// Wie lang ist eine Meile? Welche Meile gilt fⁿr Windgeschwindigkeiten?
  k=MulDiv(k,10,17);
 }
 l+=wnsprintf(s+l,slen-l,t,ws,j,k,GetStr(t,1+iMeasure),GetStr(t,3+n));
 return l;
}

#if _MSC_VER < 1400
#pragma optimize("g",off)	// sonst Problem mit DAY2FILETIME
#endif
// Liefert "Heute", "Morgen", "▄bermorgen", <Wochentag>
// bezogen auf LocalTime; <TagNr> = 0..3
int TagesName(int TagNr, TCHAR *buf, int len, HMENU hSubMenu) {
 HMENU m=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
 int l=GetMenuString(m,0x70+TagNr,buf,len,0);
 DestroyMenu(m);
 if (!StrChr(buf,'%')) return hSubMenu ? l : StripAmpersand(buf);
 else{
  SYSTEMTIME st;
  U64 ft=GetSystemFileTime(true);
  ft.ull+=DAY2FILETIME(TagNr);
  FileTimeToSystemTime(&ft.ft,&st);
  if (hSubMenu) {
   PTSTR t=StrDup(buf);
   TCHAR wd[32];
   MENUITEMINFO mii;
   GetDateFormat(LOCALE_USER_DEFAULT,0,&st,T("dddd"),wd,elemof(wd));
   wnsprintf(buf,len,t,wd);
   mii.cbSize=sizeof(mii);
   mii.fMask=MIIM_STRING;
   mii.dwTypeData=t;	// ModifyMenu() lΣsst Bitmap verschwinden
   SetMenuItemInfo(hSubMenu,0x70+TagNr,FALSE,&mii);	// QUIRKS: das & nicht auffinden!
   LocalFree(t);
   return GenerateUniqueHotkey(hSubMenu,buf,len);
  }else return GetDateFormat(LOCALE_USER_DEFAULT,0,&st,T("dddd"),buf,len)-1;
 }
}
#if _MSC_VER < 1400
#pragma optimize("g",on)
#endif

// Rechteck fⁿr einen der vier Wetter-Tapeten ermitteln (TagNr=0..3)
// bzw. eines der Sub-Rechtecke (regelt die Verteilung):
// 4 = Ungⁿltiges Wetter (Info), 16x16
// 5 = EingefΣrbter Hintergrund (H÷he = 64+FontH÷he*2+64)
// 6 = Tag-Wetter, 64x64
// 7 = Tagestemperatur
// 8 = Nacht-Wetter, 64x64
// 9 = Nachttemperatur
//10 = Niederschlagswahrscheinlichkeit, ca. 24x30
//11 = Wind, ca. 30x30?
//12 = Tagesname (Empfangszeit)
static void CalcSubRect(const RECT*src, RECT*dst, int TagNr) {
 if ((unsigned)TagNr<4) {
  const int GAP=3;	// 3 Pixel Platz zwischen den Tapeten
  int x=src->right-src->left;
  int w=(x+GAP)>>2;	// Teilung: 4 Tapeten und 3 Lⁿcken
  int a=x-((w<<2)-GAP);	// Nicht benutzte Pixel
  int l=src->left+(a>>1)+w*TagNr;	// linker Rand, das ganze zentrieren
  SetRect(dst,l,src->top,l+w-GAP,src->bottom);	// ganze H÷he benutzen
 }else{
  int h=b0size.cy;
  int m=(src->left+src->right)>>1;	// horizontale Mitte
  int b=src->top+h*3+128;		// unteres Ende des eingefΣrbten Bereichs
  int d=src->left+MulDiv(src->right-src->left,2,5);	// teilt Niederschlagswahrscheinlichkeit und Wind
  int r=(src->bottom-src->top-64-64-32)>>2;	// Platz (etwa) in der H÷he fⁿr Schrift
  if (h<16) h=16;
  if (h<r) h=r;				// Mehr Platz reservieren bei anderen DPI-Zahlen
  switch (TagNr) {
   case 4: SetRect(dst,src->right-16,src->top,src->right,src->top+16); break;
   case 5: SetRect(dst,src->left,src->top+h,src->right,b); break;
   case 6: SetRect(dst,m-32,src->top+h,m+32,src->top+h+64); break;
   case 7: SetRect(dst,src->left+1,src->top+h+64,src->right-1,b-64-h); break;
   case 8: SetRect(dst,m-32,b-64,m+32,b); break;
   case 9: SetRect(dst,src->left+1,b-64-h,src->right-1,b-64); break;
   case 10:SetRect(dst,src->left,b,d,src->bottom); break;
   case 11:SetRect(dst,d,b,src->right,src->bottom); break;
   case 12:SetRect(dst,src->left,src->top,src->right-16,src->top+h); break;
  }
 }
}

// Wetter-Block malen
// dis = ZeichenflΣche + Zielrechteck
// tag = Tag-Nr, 0 = heute, 1 = morgen usw.
// vt = Vorhersage Tag, 0 = keine, HIGHBYTE = äAlterô in Tagen, 0 = aktuell, von heute
// vn = Vorhersage Nacht, 0 = keine, HIGHBYTE genauso
static void PaintWetter(HDC dc, RECT*rc, int TagNr, U64 v) {
 WCHAR s[64];
 WCHAR t[256];			// Ressourcen-String
 int l,ww=rc->right-rc->left;	// Zur Verfⁿgung stehende Breite
 SIZE sz;			// Schiftgr÷▀e (Breite)
 RECT r;
 DWORD vt=v.Tag;
 DWORD vn=v.Nacht;

#ifdef UNICODE
#ifdef _DEBUG
 char u[64];
#endif
 l=TagesName(TagNr,s,elemof(s),0);
#else
 TCHAR u[64];
 l=TagesName(TagNr,u,elemof(u),0);
 l=MultiByteToWideChar(CP_ACP,0,u,l,s,elemof(s));
#endif

 SaveDC(dc);
 SetGraphicsMode(dc,GM_ADVANCED);

 CalcSubRect(rc,&r,5);
 GradientFillRect(dc,&r,RGB(192,192,255),RGB(128,192,128),900);

// SetViewportOrgEx(dc,rc->left,rc->top,NULL);

 if (vt>>24>=24 || vn>>24>=24) {
  ww-=16;	// weniger Platz fⁿr String!
  CalcSubRect(rc,&r,4);
  DrawIconEx(dc,r.left,r.top,LoadIcon(0,IDI_INFORMATION),16,16,0,0,DI_NORMAL);
 }

 SetTextAlign(dc,TA_TOP|TA_LEFT);
 SelectFont(dc,gdi.fnb);
 GetTextExtentPoint32W(dc,s,l,&sz);
 if (sz.cx>=ww) {		// Platz knapp?
  SelectFont(dc,gdi.fnn);	// "Arial Narrow" benutzen
 }
 TextOutW(dc,rc->left+1,rc->top,s,l);

 MyLoadStringW(51,t,elemof(t));
 SetTextAlign(dc,TA_TOP|TA_CENTER);
 if (vt) {
  WCHAR u[32];
  CalcSubRect(rc,&r,7);
  SelectFont(dc,gdi.fnb);
  TempDegW(vt,u,elemof(u));
  l=wnsprintfW(s,elemof(s),t,u);		//"Tag: "
  GetTextExtentPoint32W(dc,s,l,&sz);
  if (sz.cx>r.right-r.left) SelectFont(dc,gdi.fnn);
  TextOutW(dc,(r.left+r.right)>>1,r.top,s,l);
 }
 if (vn) {
  WCHAR u[32];
  CalcSubRect(rc,&r,9);
  SelectFont(dc,gdi.fnb);
  TempDegW(vn,u,elemof(u));
  l=wnsprintfW(s,elemof(s),GetStrW(t,1),u);	//"Nacht: "
  GetTextExtentPoint32W(dc,s,l,&sz);
  if (sz.cx>r.right-r.left) SelectFont(dc,gdi.fnn);
  TextOutW(dc,(r.left+r.right)>>1,r.top,s,l);
 }
 CalcSubRect(rc,&r,6);
 SetViewportOrgEx(dc,(r.left+r.right)>>1,(r.top+r.bottom)>>1,NULL);
 ptSymbolTag(dc,v);
 CalcSubRect(rc,&r,8);
 SetViewportOrgEx(dc,(r.left+r.right)>>1,(r.top+r.bottom)>>1,NULL);
 ptSymbolNacht(dc,v,TagNr);

 SetMapMode(dc,MM_ISOTROPIC);
 SetWindowExtEx(dc,64,64,NULL);
 SetViewportExtEx(dc,32,32,NULL);		// Gr÷▀e halbieren und Kreise garantieren
 if (vt) {
  CalcSubRect(rc,&r,10);
  SetViewportOrgEx(dc,(r.left+r.right)>>1,r.top+MulDiv(r.bottom-r.top,3,5),NULL);
  ptNSW(dc,v);			// Niederschlagswahrscheinlichkeit
 }
 if (vn) {
  CalcSubRect(rc,&r,11);
  SetViewportOrgEx(dc,(r.left+r.right)>>1,(r.top+r.bottom)>>1,NULL);
  ptRose(dc);
  ptWind(dc,v);		// Windpfeil
 }
 RestoreDC(dc,-1);

#ifdef _DEBUG
 if (vt) {
  TextOutA(dc,rc->left,16, u,wnsprintfA(u,elemof(u),"0x%X",vt&0xFFFFFF));
  TextOutA(dc,rc->left,64, u,wnsprintfA(u,elemof(u),"Alter = %d h",vt>>24));
 }
 if (vn) {
  TextOutA(dc,rc->left,112,u,wnsprintfA(u,elemof(u),"0x%X",vn&0xFFFFFF));
  TextOutA(dc,rc->left,160,u,wnsprintfA(u,elemof(u),"Alter = %d h",vn>>24));
 }
#endif
}

// Siehe Flaggen.txt. Die Zuordnung bezieht sich auf den Mess-Ort (bspw. Erzgebirge, Decin -> Tschechien)
const BYTE Flaggen[90]={
 10,10,10,10,10,10, 3,10,10,10,
 10,28,11,11,11,31,31,10,31,11,
  7, 7,11, 7,11,11,11,15,11,11,
 11,28,28,28,28,28,28,11,28,15,
 15,15,18,15,15,10, 1, 1, 1,27,
  6, 6,11,29,29,29,29,11,22,11,
 15,15,15,12,26,26,15,26,26,26,
  0,26,24,15,26,13,13,31,22,22,
 29,19,19,19,29,29,28, 5,28, 5};

// Fensterposition links-unten, Bildschirmkoordinaten, in POINTS (short)
static DWORD GetWndPos(HWND w) {
 RECT r;
 GetWindowRect(w,&r);
 return MAKELONG(r.left,r.bottom);
}

static int CompareItem(const COMBOBOXEXITEM *i1, const COMBOBOXEXITEM *i2) {
 int ret=0;
 BYTE cbSort=Config.cbSort;	// an lokaler Kopie arbeiten
 if (cbSort&0x10 && i1->iImage!=i2->iImage) {	// vorsortieren?
  TCHAR s1[64], s2[64];	// LΣndernamen
  if (LoadString(ghInstance,352+i1->iImage,s1,elemof(s1))
  && LoadString(ghInstance,352+i2->iImage,s2,elemof(s2))) ret=lstrcmpi(s1,s2);
  else ret=Kennzeichen[i1->iImage]-Kennzeichen[i2->iImage];
 }
 if (!ret) switch (cbSort&0x60) {	// Bei Gleichheit im Vorvergleich
  case 0x00: ret=(int)i1->lParam-(int)i2->lParam; break;
  case 0x20: {
   PCTSTR p1=i1->pszText,p2=i2->pszText;
   if (Config.cbSort&2) {	// mit Nummer?
    PCTSTR p=StrChr(p1,':');
    if (p) p1=p+2;		// hinter ' '
    p=StrChr(p2,':');
    if (p) p2=p+2;
   }
   ret=lstrcmpi(p1,p2);
  }break;
  case 0x40: ret=GetCityX((BYTE)i1->lParam)-GetCityX((BYTE)i2->lParam); break;
  case 0x60: ret=GetCityY((BYTE)i1->lParam)-GetCityY((BYTE)i2->lParam); break;
 }
 if (cbSort&0x80) ret=-ret;
 return ret;
}

// Nach Wⁿste-halbieren-Methode Index zum Einfⁿgen finden
// Bei Gleichheit sollte dahinter eingeordnet werden
static int FindIndex(HWND w, const COMBOBOXEXITEM *i2) {
 int m=0;
 int l=0,r=(int)i2->lParam;
 COMBOBOXEXITEM i1;
 TCHAR s[128];
 i1.mask=CBEIF_TEXT|CBEIF_IMAGE|CBEIF_LPARAM;
 i1.pszText=s;
 i1.cchTextMax=elemof(s);
 while (m=(l+r)>>1,l<r) {
  i1.iItem=m;
  SendMessage(w,CBEM_GETITEM,0,(LPARAM)&i1);
  if (CompareItem(&i1,i2)<0) l=m+1; else r=m;
 }
 return m;
}

static void FillCombo(HWND w) {
 int i;
 ComboBox_ResetContent(w);
 for (i=0; i<=90; i++) {
  COMBOBOXEXITEM cbei;
  TCHAR s[128], *sp=s;	// Gebiet [\0 Stadt]
  cbei.lParam=i;
  cbei.mask=CBEIF_TEXT|CBEIF_LPARAM;
  if (i==90) {
   if (Config.uRegion<0) break;
   if (hFirst) {	// Karte vorhanden? (Liefert Land zur User-Koordinate)		
    cbei.iImage=(int)SendMessage(hFirst,WM_MyPosStaat,0,0);
    if (cbei.iImage>=0) {
     cbei.mask|=CBEIF_IMAGE|CBEIF_SELECTEDIMAGE;
    }
   }
  }else{
   cbei.iImage=Flaggen[i];
   cbei.mask|=CBEIF_IMAGE|CBEIF_SELECTEDIMAGE;
  }
  cbei.iSelectedImage=cbei.iImage;
  s[LoadString(ghInstance,256+i,s,elemof(s)-1)+1]=0;
  if (Config.cbSort&1) {
   sp=s+lstrlen(s)+1;	// Stadt anzeigen
   if (!*sp) sp=s;
  }
  if (Config.cbSort&2) {
   PTSTR n=StrDup(sp);
   if (n) {
    wnsprintf(s,elemof(s),T("%d: %s"),i==90?Config.uRegion:i,n);
    sp=s;		// Nummer davorsetzen
    LocalFree(n);
   }
  }
  if (Config.cbSort&4) {
   PointF pos=GetCityKoord((BYTE)i);
   PTSTR a=sp+lstrlen(sp);	// AnhΣnge-Position
   TCHAR sx[8],sy[8];
   FloatToStr(pos.X,1,sx,elemof(sx));
   FloatToStr(pos.Y,1,sy,elemof(sy));
   wnsprintf(a,(int)(s+elemof(s)-a),T(" (%s%s/%s%s)"),sx,sGrad,sy,sGrad);
  }
  cbei.pszText=sp;
  cbei.iItem=FindIndex(w,&cbei);
  SendMessage(w,CBEM_INSERTITEM,0,(LPARAM)&cbei);
  if (i==Config.Region) ComboBox_SetCurSel(w,cbei.iItem);
 }
}

// Ziel dieser ComboBoxEx-Unterklasse ist, dass beim rechten Mausklick
// sofort das Kontextmenⁿ erscheint, und nicht erst, wenn man mit der Maus etwas zieht.
static WNDPROC ComboBoxOrig;
static INT_PTR CALLBACK ComboBoxProc(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam) {
 switch (Msg) {
  case WM_RBUTTONDOWN: {
   SendMessage(Wnd,WM_CONTEXTMENU,(WPARAM)GetParent(Wnd),ClientToScreenS(Wnd,(DWORD)lParam));
  }break;
 }
 return CallWindowProc(ComboBoxOrig,Wnd,Msg,wParam,lParam);
}

static HWND hTip;
//static TCHAR tips[2048], *ptips;	// Puffer und Fⁿllzeiger fⁿr alle Strings

/*
static bool AnyForecast(const U64 wetter[4]) {
 int i;
 for (i=0; i<4; i++) if (wetter[i].ull) return true;
 return false;
}
*/
static int AddAufUnterText(PTSTR s, int slen, bool bSonne, DWORD MJD) {
 TCHAR Auf[6],Unter[6],t[128];
 bool bSwap;
 int l=lstrlen(s);
 if (l && slen>l+1) s[l++]='\n';
 bSwap=AufUntergang(bSonne,MJD,Auf,Unter);
 LoadString(ghInstance,63,t,elemof(t));
 l+=wnsprintf(s+l,slen-l,GetStr(t,bSonne*2+bSwap),bSwap?Unter:Auf);
 if (slen>l+1) s[l++]='\n';
 l+=wnsprintf(s+l,slen-l,GetStr(t,bSonne*2+1-bSwap),bSwap?Auf:Unter);
 return l;
}

static int Annotation(PTSTR s, int slen, U64 v) {
 int l=0;
 int at=v.Tag>>24;	// Alter in Stunden (FIXME: Keine Konstante!)
 int an=v.Nacht>>24;
 if (v.Tag && v.Nacht && (v.Tag^v.Nacht)&0xFF && at==an+3) {
  l=LoadString(ghInstance,61,s,slen);
 }
 if (at<an) at=an;	// die Σltere der beiden Infos
 if (at) {
  TCHAR t[64];
  if (l && slen>l+1) s[l++]='\n';
  LoadString(ghInstance,62,t,elemof(t));
  l+=wnsprintf(s+l,slen-l,t,at);
 }
 return l;
}

static int Empfangszeit(PTSTR s, int slen, int region, int tagnr) {
 SYSTEMTIME st;
 TCHAR s1[32],t[128];
 int index=region<60?region+120*tagnr:420+region-60+30*tagnr;
 div_t d;
 index-=(geotz+120)/3;
 LoadString(ghInstance,50,t,elemof(t));
 d=udiv(index*3+2,1440);
 index=d.rem;		// in Bereich 0..1339 (Tagesminute) ziehen
 d=udiv(index,60);	// Stunden, Minuten
 __stosd((DWORD*)&st,0,sizeof(st)/4);	// weglassen??
 st.wHour=d.quot;
 st.wMinute=d.rem;
 st.wSecond=15;
 GetTimeFormat(LOCALE_USER_DEFAULT,0,&st,NULL,s1,elemof(s1));
 if (region<60 && tagnr<3) {
  int l;
  StrCatBuff(s1,GetStr(t,1),elemof(s1));	// " / " anhΣngen
  l=lstrlen(s1);
  st.wHour=(d.quot+3)%24;
  GetTimeFormat(LOCALE_USER_DEFAULT,0,&st,NULL,s1+l,elemof(s1)-l);
 }
 return wnsprintf(s,slen,t,s1);
}

static int NSW(PTSTR s, int slen, DWORD vt) {
 TCHAR t[64];
 LoadString(ghInstance,56,t,elemof(t));
 return wnsprintf(s,slen,t,getNSW(vt));
}

static void BuildTips(HWND Wnd, int region) {
// <geoloc> (siehe Mondalter.c) auf Koordinaten der Stadt setzen,
// damit die Uhrzeitangaben fⁿr Sonnen- und Mond-Auf- und UntergΣnge stimmen
 U64 wetter[4];
 RECT wr;
 int idx;
 int JD1858;	// Julianisches Datum bzgl. Mittwoch, 1858/11/17 00:00 UT1 
 if (hTip && DestroyWindow(hTip)) hTip=0;
 geoloc=GetCityKoord((BYTE)region);
 if (!GetForecast(region,wetter)) return;
 hTip=CreateWindowEx(WS_EX_TOPMOST|WS_EX_TOOLWINDOW,TOOLTIPS_CLASS,NULL,
   WS_POPUP|TTS_NOPREFIX|TTS_ALWAYSTIP,0,0,0,0,Wnd,NULL,ghThisInst,NULL);
 SendMessage(hTip,TTM_SETMAXTIPWIDTH,0,300);
 JD1858=GetHeuteMJD(true)-94187;
 GetWindowRect(GetDlgItem(Wnd,11),&wr);
 MapWindowPoints(0,Wnd,(PPOINT)&wr,2);
 for (idx=0; idx<4; idx++,JD1858++) {
  U64 v=wetter[idx];
  if (v.ull) {
   RECT r;
   TCHAR s[256];
   TOOLINFO ti;
   int l;
   CalcSubRect(&wr,&r,idx);
   InitStruct(&ti,sizeof(ti));
   ti.hwnd=Wnd;
   ti.lpszText=s;
   CalcSubRect(&r,&ti.rect,12);
   Empfangszeit(s,elemof(s),region,idx);
   SendMessage(hTip,TTM_ADDTOOL,0,(LPARAM)&ti);
   l=Annotation(s,elemof(s),v);
   if (l) {
    CalcSubRect(&r,&ti.rect,4);
    SendMessage(hTip,TTM_ADDTOOL,0,(LPARAM)&ti);
   }
   CalcSubRect(&r,&ti.rect,6);
   GenWSymText(s,elemof(s),false,v);
   AddAufUnterText(s,elemof(s),true,JD1858);
   SendMessage(hTip,TTM_ADDTOOL,0,(LPARAM)&ti);
   CalcSubRect(&r,&ti.rect,8);
   GenWSymText(s,elemof(s),true,v);
   AddAufUnterText(s,elemof(s),false,JD1858);
   SendMessage(hTip,TTM_ADDTOOL,0,(LPARAM)&ti);
   if (v.Tag) {
    CalcSubRect(&r,&ti.rect,10);
    l=NSW(s,elemof(s),v.Tag);
    SendMessage(hTip,TTM_ADDTOOL,0,(LPARAM)&ti);
   }
   if (v.Nacht) {	//Nacht: Wind
    CalcSubRect(&r,&ti.rect,11);
    GenWindText(s,elemof(s),v.Nacht);
    SendMessage(hTip,TTM_ADDTOOL,0,(LPARAM)&ti);
   }
  }
 }
}

// Regionsnummer von der Combobox erfragen (0..90)
// (beim Hot-Tracking nicht gleich Config.Region!)
static int GetCurRegion(HWND Wnd) {
 COMBOBOXEXITEM cbei;
 HWND w=GetDlgItem(Wnd,12);
 cbei.iItem=ComboBox_GetCurSel(w);
 cbei.mask=CBEIF_LPARAM;
 SendMessage(w,CBEM_GETITEM,0,(LPARAM)&cbei);
 return (int)cbei.lParam;
}


// Wird aufgerufen
// - beim Initialisieren des Fensters
// - wenn sich GetCurRegion() oder Config.Region oder - falls jeweils =90, Config.uRegion Σndert
// - wenn bei der aktuellen Region etwas empfangen wurde = WM_FUNKRECV(16)
// - beim Tageswechsel (Lokalzeit) = WM_FUNKRECV(17)
static void UpdateForecast(HWND Wnd) {
 BuildTips(Wnd,GetCurRegion(Wnd));
 InvalidateRect(GetDlgItem(Wnd,11),NULL,TRUE);	// Wetteranzeige aktualisieren
}

// Hot-Tracking-Region setzen
void SetCurRegion(HWND Wnd, int region) {
 COMBOBOXEXITEM cbei;
 int k;
 HWND w=GetDlgItem(Wnd,12);
 if ((unsigned)region>90) region=Config.Region;
 k=ComboBox_GetCount(w);
 cbei.mask=CBEIF_LPARAM;
 for (cbei.iItem=0; cbei.iItem<k; cbei.iItem++) {
  SendMessage(w,CBEM_GETITEM,0,(LPARAM)&cbei);
  if ((int)cbei.lParam==region) {
   ComboBox_SetCurSel(w,cbei.iItem);
   UpdateForecast(Wnd);
   return;
  }
 }
}

INT_PTR CALLBACK WetterDlgProc(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam) {
 DefHelpProc(Wnd,Msg,lParam,108);
 if (hTip && WM_MOUSEFIRST<=Msg && Msg<=WM_MOUSELAST) {
  MSG msg={Wnd,Msg,wParam,lParam,GetMessageTime(),GetMessagePos()};
  SendMessage(hTip,TTM_RELAYEVENT,0,(LPARAM)&msg);
 }
 switch (Msg) {
  case WM_INITDIALOG: {
   HWND w=GetDlgItem(Wnd,12);
   HIMAGELIST il=ImageList_LoadBitmap(ghThisInst,MAKEINTRESOURCE(1),16,32,RGB(255,0,255));
   CreateSymbGdiHandles();
   SendMessage(w,CBEM_SETIMAGELIST,0,(LPARAM)il);
   ComboBoxOrig=SubclassWindow(GetFirstChild(w),ComboBoxProc);
   FillCombo(w);
   CalcMondalter();
   UpdateForecast(Wnd);
  }return TRUE;

  case WM_COMMAND: switch (LOWORD(wParam)) {
   case 12: switch (HIWORD(wParam)) {	// Combobox:
    case CBN_SELCHANGE: {		// Auswahl-Wechsel von Hand
     InfoPropSheet(18,Config.Region);
     Config.Region=GetCurRegion(Wnd);
     UpdateForecast(Wnd);
    }break;
   }break;
   case 13: {
    HWND prev=hFirst;
    SendMessage(Karte(),WM_FocusRegion,Config.Region,0);
    if (Config.uRegion>=0 && !prev && hFirst) FillCombo(GetDlgItem(Wnd,12));
   }break;
  }break;

  case WM_CONTEXTMENU: if ((HWND)wParam==GetDlgItem(Wnd,12)) {
   BYTE cbSort=Config.cbSort;	// an lokaler Kopie arbeiten
   HMENU m=LoadMenu(ghInstance,MAKEINTRESOURCE(108));
   HMENU sm=GetSubMenu(m,0);
   CheckMenuRadio(sm,10+(cbSort&1));
   if (cbSort&2) CheckMenuItem(sm,12,MF_CHECKED);
   if (cbSort&4) CheckMenuItem(sm,13,MF_CHECKED);
   if (cbSort&0x10) CheckMenuItem(sm,16,MF_CHECKED);
   CheckMenuRadio(sm,17+(cbSort>>5&3));
   if (cbSort&0x80) CheckMenuItem(sm,21,MF_CHECKED);
   if (lParam==(LPARAM)-1) lParam=GetWndPos((HWND)wParam);
   switch (lParam=TrackPopupMenu(sm,TPM_RIGHTBUTTON|TPM_RETURNCMD,
     GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),0,(HWND)wParam,NULL)) {
    case 10:
    case 11: cbSort=cbSort&~1|(BYTE)lParam-10; break;
    case 12: cbSort^=2; break;
    case 13: cbSort^=4; break;
    case 16: cbSort^=0x10; break;
    case 17:
    case 18:
    case 19:
    case 20: cbSort=cbSort&~0x60|((BYTE)lParam-17)<<5; break;
    case 21: cbSort^=0x80; break;
   }
   DestroyMenu(m);
   Config.cbSort=cbSort;
   FillCombo((HWND)wParam);
  }break;

  case WM_DRAWITEM: {
   LPDRAWITEMSTRUCT dis=(LPDRAWITEMSTRUCT)lParam;
   U64 wetter[4];
   SetBkMode(dis->hDC,TRANSPARENT);
   if (GetForecast(GetCurRegion(Wnd),wetter)) {
    int idx;
    for (idx=0; idx<elemof(wetter); idx++) {
     U64 v=wetter[idx];
     if (v.ull) {
      RECT r;
      CalcSubRect(&dis->rcItem,&r,idx);
      PaintWetter(dis->hDC,&r,idx,v);	// Wetter-äTapetenstreifenô ausgeben
     }
    }
   }else{
    TCHAR s[80];
    int l;
    SaveDC(dis->hDC);
    l=LoadString(ghInstance,49,s,elemof(s));	// "Keine Wetterdaten!"
    SetTextColor(dis->hDC,GetSysColor(COLOR_GRAYTEXT));
    SelectFont(dis->hDC,gdi.fnr[1]);	// gro▀e Schrift
    InflateRect(&dis->rcItem,-20,-20);
    DrawText(dis->hDC,s,l,&dis->rcItem,DT_CENTER|DT_NOPREFIX|DT_WORDBREAK);
    RestoreDC(dis->hDC,-1);
#if 0
    len=(dis->rcItem.right-dis->rcItem.left+1)/4;	// Spaltenbreite (bei mir: 79)
    for (i=0; i<12; i++) {
     SetViewportOrgEx(dis->hDC,i%4*len+len/2,32+i/4*66,NULL);
     Rectangle(dis->hDC,-32,-32,32,32);
     SaveDC(dis->hDC);
     ptSymb(dis->hDC,i);
     RestoreDC(dis->hDC,-1);
    }
#endif
   }
  }break;
  
  case WM_FUNKRECV: switch ((BYTE)wParam) {
   case 16: {
    DWORD d=IndexToRegion((DWORD)lParam);
    int r=Config.Region;
    if (r==90) r=Config.uRegion;
    if (LOBYTE(HIWORD(d))!=r) break;
   }nobreak;
   case 17: CalcMondalter(); UpdateForecast(Wnd); break;	// Mitternacht
   case 19: {
    FillCombo(GetDlgItem(Wnd,12));	// neu fⁿllen
    UpdateForecast(Wnd);	// Mⁿ▀ig, hier herauszufinden, ob das n÷tig ist
   }break;
  }break;

  case WM_DESTROY: {
   DeleteSymbGdiHandles();
   if (hTip) DestroyWindow(hTip);
   ImageList_Destroy((HIMAGELIST)SendDlgItemMessage(Wnd,12,CBEM_GETIMAGELIST,0,0));
  }break;
 }
 return FALSE;
}
Detected encoding: OEM (CP437)1
Wrong umlauts? - Assume file is ANSI (CP1252) encoded