/********************************
* 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
|
|