/********************************
* Projekt: Funkuhr DCF77 *
* Europakartendarstellung *
* zur Vorhersageregionsauswahl *
* und Wetterkarte wie im TV *
********************************/
#include "Funkuhr.h"
#include <math.h>
#include <olectl.h>
#define SETMIN(a,b) if ((a)>(b)) (a)=(b)
#define SETMAX(a,b) if ((a)<(b)) (a)=(b)
void InflateRectForPoint(RECT*r, const POINT*p) {
SETMIN(r->left ,p->x);
SETMIN(r->top ,p->y);
SETMAX(r->right ,p->x);
SETMAX(r->bottom,p->y);
}
typedef struct{
int Z,N;
}scale_t;
HWND hFirst;
const TCHAR KARTECLASSNAME[]=T("HSW");
#ifndef UNICODE
static bool NoAlpha; // Win98-Hack
#endif
// Speicher-Daten, in Karten-Pixel umgerechnet
typedef struct{
int nPoly; // Anzahl Poly[]-Punkte
char CitySize; // Bezugsort-Größe aus Ressource, nach Einwohnern, bspw. 10 = 1-2 Mio
//char TextPos; // Vorschlag für Text um die Stadt herum, 0 = Nord, 1=NO, 2=Ost usw.
//char rsv[2];
BYTE fix; // Bit 0 = rcText ist festgelegt, unverschieblich
// Bit 1 = rcSymb ist festgelegt, unverschieblich
POINT CityPos; // Vorhersage-Bezugsort, in Bitmap-Pixel
// Die folgenden 4 Rechtecke liegen in unverschobenen Client-Koordinaten vor,
// ändern sich also mit der Skalierung oder dem Hot-Tracking, aber nicht beim Scrollen,
// außer wenn sich Texte oder Wettersymbole nach außerhalb bewegen
RECT rcCity; // Platz für Stadtklecks
RECT rcText; // okkupierter Platz für den Text
RECT rcSymb; // okkupierter Platz für's Wettersymbol
RECT rcPoly; // reichliches Umfangsrechteck für Polygon (u.a. darf da das Wettersymbol nicht raus)
POINT Centroid; // Schwerpunkt der Polygonfläche, in Bitmap-Pixel
int Area; // vorzeichenbereinigter Flächeninhalt der Polygonfläche, in Bitmap-Quadratpixel
int rsv1;
POINT Poly[];
}MAPMEM;
typedef struct{ // "Expandierte" Grenzen-Koordinaten
union{
struct{
WORD n; // Anzahl Linien-Punkte, >=2, 0 = Ende der Liste
char staaten[2]; // Staat links/rechts
};
DWORD hdr; // Header genauso wie in Ressource
BOOL valid; // Endekennung (wenn obige Angaben Null sind)
};
POINT points[]; // Punktliste in Bitmap-Pixel
}BORDEREX;
static BORDEREX*IterateBorderex(const BORDEREX*bo) {
if (bo) (BYTE*)bo+=sizeof(BORDEREX)+bo->n*sizeof(POINT);
return (BORDEREX*)bo;
}
// Karten-Info (also je nach Bitmap oder Vektorgrafik)
// … so was ähnliches wie eine Virtuelle Methodentabelle
typedef struct _KINFO{
PCTSTR name; // ist ohnehin hartkodiert! NULL für "leeres" KINFO
SIZE szBitmap; // für EraseBkgnd (nicht diesen Bereich löschen, Flackern vermeiden, {0,0} bei WMF)
PointF (*kMap)(PointF); // Umrechnungsprozedur Länge/Breite in Pixel
PointF (*kUnmap)(PointF); // Umrechnungsprozedur Pixel in Länge/Breite
void (*kGradnetz)(const struct _KINFO*,HDC dc); // Gradnetz zeichnen (je nach Projektionsart)
}KINFO;
// so 'ne Art Objekt
typedef struct{
HWND hMain,hMap,hMini,hToolbar,hStatus,hTip; // Fenster-Handles, hMap kann gleich hMain sein, beide sind niemals 0
const KINFO *kInfo; // [18] kein NULL-Pointer
POINT MousePos; // [1C] Mausposition in hMap-Client-Koordinaten (hMini muss extra geklärt werden!)
char HoverIndex; // [24] -1..90
BYTE kShow; // [25] 6 lokale Config-Kopien
char kMini;
BYTE kVis;
char kScale;
BYTE kAutoScale;
BYTE kWetter;
char HoverStaat; // [2B] -1(Meer) .. 46 (Kosovo)
bool bShifted:1; // [2C] Geschoben mit linker Maustaste? Wenn nicht beim Loslassen Config.Region setzen
bool bFromMouse:1; // Mausereignis? Mausposition ams Zentrum benutzen. Sonst Zentrum = Client-Mitte
bool bInitialized:1; // <map> und <gdi> initialisiert
bool bPictureHBM:1; // hPicture ist statt pPicture gültig
bool bReserved:4;
BYTE Recursion; // [2D] Rekursionszähler für WM_SIZE
char MiniScale; // [2E] Standard: ~8 (Von der Bildgröße und Bildschirmgröße abhängig.)
char MyPosStaat; // [2F] -1..NUMSTAAT-1 = Hittest-Ergebnis für "meine Position"
SCROLLINFO ScrollInfo[2];//[30] Die großen Batzen hinten (Hilft das?)
RECT ScrollRect; // [68] Client-Bereich ohne Toolbar und Statuszeile (und ohne die Scrollbalken??)
scale_t Scale; // [78]
RECT MapExtent; // [80] Größe der Map oder des Hintergrundbildes (für Rollbalken), unskaliert
union{
IPicture *pPicture; // [90]
HBITMAP hPicture;
};
POINT kMiniPos; // [94] Position des freien Miniatur-Fensters, in Bildschirmkoordinaten
HBITMAP MiniBmp; // [9C] Cache der Bitmap des Hintergrunsbildes für das Mini-Fenster
HIMAGELIST flags; // [A0]
MAPMEM *map[91]; // [A4] Vorhersageregionen, Koordinaten auf pPicture umgerechnet
HRGN staat[NUMSTAAT]; //[210] Vorgehaltene Regionen für Treffertests und zum Ausmalen
TCHAR strTip[256]; //[32C] Vorgehaltener fliegender Text
TCHAR strStatus4[128]; //[52C] Vorgehaltener Ownerdraw-Text für Status-Feld 4
BORDEREX bo/*[]*/; //[62C] Vorgehaltene Punktliste der Küsten und Grenzen, pPicture-Koordinaten
}karte;
static void LoadClientRect(HWND Wnd, RECT *rc) {
static const char ControlsList[]={0,0,true,10,true,11,false};
int Controls[elemof(ControlsList)];
bloat(Controls,ControlsList,elemof(Controls));
GetEffectiveClientRect(Wnd,rc,Controls);
}
// "vormultipliziertes" RGB+Alpha-Quadrupel erzeugen
#define RGBA(r,g,b,a) (COLORREF)MAKELONG(MAKEWORD(r*(a)/255,g*(a)/255),MAKEWORD(b*(a)/255,a))
static IPicture* LoadPictureFile(LPCTSTR szFile) {
IPicture *ret=NULL;
IStream *pstm;
if (SUCCEEDED(SHCreateStreamOnFile(szFile,0,&pstm)) && pstm) {
if (!SUCCEEDED(OleLoadPicture(pstm,0,FALSE,&IID_IPicture,&ret))) ret=NULL;
(*(IStreamVtbl**)pstm)->Release(pstm);
}
return ret;
}
/*************************************************
** Küsten- und Grenzverläufe (Eigenbau) **
*************************************************/
typedef struct{
char x,y;
}POINTC;
typedef struct{
union{
struct{
WORD n; // Anzahl Punkte insgesamt, 0 = Listen-Ende
char staaten[2]; // Staat auf linker/rechter Seite der Grenze
};
DWORD hdr;
};
POINTS start; // Startkoordinate, Insel/Exklave wenn gleich Ende
POINTC Diff[]; // n-1 Punkt-Differenzen
}BORDER;
// Border-Zeiger um <index> Einträge vorrücken
static const BORDER *IterateBorder(const BORDER*bo) {
if (bo) (BYTE*)bo+=sizeof(BORDER)+(bo->n-1)*sizeof(POINTC);
return bo;
}
// Borderdaten beschaffen
static const BORDER *GetBorder() {
static HGLOBAL bo; // Ressourcen-Daten, durch valid=0 beendet
if (!bo) bo=LoadResource(0,FindResource(0,MAKEINTRESOURCE(49),RT_RCDATA));
return LockResource(bo);
}
// Berechnet Platz für LocalAlloc()
static int CalcBorderexSize() {
int ret=4; // für die Ende-Kennung
const BORDER*bo;
for (bo=GetBorder(); bo->n; bo=IterateBorder(bo)) {
ret+=sizeof(BORDEREX)+bo->n*sizeof(POINT);
}
return ret;
}
static bool ComparePoint(const POINT*p1, const POINT*p2) {
return abs(p1->x-p2->x)<3 && abs(p1->y-p2->y)<3;
}
typedef struct _POLYJOIN { // repräsentiert ein Linienstück
struct _POLYJOIN *next, *prev;
const BORDEREX*bo;
bool reverse; // wenn das Land rechtsseitig ist
}POLYJOIN;
typedef struct _POLYSEARCH { // repräsentiert ein Polygon
struct _POLYSEARCH *next, *prev;
const POINT *A, *E; // derzeitiger Anfangs- und Endpunkt
POLYJOIN *a, *e; // die beiden Enden der POLYJOIN-Liste
}POLYSEARCH;
// + = hinten, - = vorn
// rechts = Landseite der Grenze <bo>, false=links
static char CanAttach(const POLYSEARCH*ps, const BORDEREX*bo, bool reverse) {
// if (ComparePoint(ps->enden+0,ps->enden+1)) return 0; // Geschlossene Figur
if (!reverse && ComparePoint(ps->E,bo->points)) return 1; // vorwärts am Ende
if (reverse && ComparePoint(ps->E,bo->points+bo->n-1)) return 1; // rückwärts am Ende
if (!reverse && ComparePoint(ps->A,bo->points+bo->n-1)) return -1; // vorwärts am Anfang
if (reverse && ComparePoint(ps->A,bo->points)) return -1; // rückwärts am Anfang
return 0;
}
static void CreateStaatenRegions(karte*K) {
char staat;
// Ich erspare mir den Ärger, den Datenbaum einzeln abräumen zu müssen
for (staat=0; staat<NUMSTAAT; staat++) {
// 1. Polygone und Punkte für das Polygon zählen
HANDLE heap=HeapCreate(HEAP_GENERATE_EXCEPTIONS|HEAP_NO_SERIALIZE,1,0);
POLYSEARCH*list=NULL;
const BORDEREX*bo=&K->bo;
for(;bo->valid;bo=IterateBorderex(bo)) {
POLYSEARCH*ps;
bool reverse=bo->staaten[1]==staat;
if (reverse || bo->staaten[0]==staat) {
POLYJOIN*pj=HeapAlloc(heap,HEAP_GENERATE_EXCEPTIONS|HEAP_NO_SERIALIZE|HEAP_ZERO_MEMORY,sizeof*pj);
pj->bo=bo;
pj->reverse=reverse;
for (ps=list; ps; ps=ps->next) {
char a=CanAttach(ps,bo,reverse);
if (a) {
POLYSEARCH*ps2;
if (a<0) { // vorn
ps->a->prev=pj;
pj->next=ps->a;
ps->A=&bo->points[reverse?bo->n-1:0];
ps->a=pj;
}else{ // hinten
ps->e->next=pj;
pj->prev=ps->e;
ps->E=&bo->points[reverse?0:bo->n-1];
ps->e=pj;
}
for(a=0, ps2=ps->next; ps2; ps2=ps2->next) { // Freie Stücken anketten, wenn's passt
if (ComparePoint(ps->E,ps2->A)) a=1; // ps2 passt hinten dran
else if (ComparePoint(ps->A,ps2->E)) a=-1; // ps2 passt vorn dran
if (a) {
if (ps2->next) ps2->next->prev=ps2->prev; // ps2 aus Liste ausketten
if (ps2->prev) ps2->prev->next=ps2->next;
if (a<0) { // an ps vorn anketten
ps2->e->next=ps->a; // Sub-Kette um-hängen
ps->a->prev=ps2->e;
ps->a=ps2->a;
ps->A=ps2->A;
}else{ // an ps hinten anketten
ps2->a->prev=ps->e;
ps->e->next=ps2->a;
ps->e=ps2->e;
ps->E=ps2->E;
} // Jetzt könnte man ein HeapFree() machen...
break;
}
}
goto brk;
}
}
// Ein neues Polygon beginnen
ps=HeapAlloc(heap,HEAP_GENERATE_EXCEPTIONS|HEAP_NO_SERIALIZE|HEAP_ZERO_MEMORY,sizeof*ps);
if (list) list->prev=ps;
ps->next=list;
list=ps;
ps->A=&bo->points[reverse?bo->n-1:0];
ps->E=&bo->points[reverse?0:bo->n-1]; // das gesuchte Land sei im Normalfall _links_
ps->a=ps->e=pj;
brk:;
}//if (Staatsgrenze passt)
}//for (alle Grenzlinien)
// Nun sollten im Idealfall ein oder mehrere (Inseln, Seen) _geschlossene_ (*A==*E) Polygone übrig bleiben.
// Bei offenem Polygon wird mit einer Geraden geschlossen (Russland).
if (list) {
int ip=0,ig=0,*ii,*il;
POINT*pi,*pl;
const POLYSEARCH*ps;
for (ps=list;ps;ps=ps->next) {
POLYJOIN*pj;
ip++; // Polygone zählen
for (pj=ps->a; pj; pj=pj->next)
ig+=pj->bo->n; // Gesamtpunkte zählen
}
il=HeapAlloc(heap,HEAP_GENERATE_EXCEPTIONS|HEAP_NO_SERIALIZE|HEAP_ZERO_MEMORY,ip*sizeof*il); //**-Crypt??
pl=HeapAlloc(heap,HEAP_GENERATE_EXCEPTIONS|HEAP_NO_SERIALIZE|HEAP_ZERO_MEMORY,ig*sizeof*pl);
for (ii=il,pi=pl,ps=list;ps;ii++,ps=ps->next) { // Punkte zusammenkopieren
POLYJOIN*pj;
for (pj=ps->a; pj; pj=pj->next) {
*ii+=pj->bo->n; // Doppelpunkte ignorieren
if (pj->reverse) {
int i;
for (i=0; i<pj->bo->n; i++) *pi++=pj->bo->points[pj->bo->n-1-i]; // Punktreihenfolge umkehren
}else{
__movsd((DWORD*)pi,(DWORD*)pj->bo->points,pj->bo->n*sizeof*pi/sizeof(DWORD));
pi+=pj->bo->n; // Punktreihenfolge belassen
}
}
}
K->staat[staat]=CreatePolyPolygonRgn(pl,il,ip,ALTERNATE);
}
HeapDestroy(heap);
}
}
/*************************************************
** Google Maps Overlay (meteotime.com) **
*************************************************/
// Ressourcen-Daten, in Mercator-Pixeln
typedef struct{
BYTE nPoly;
BYTE CitySize; // High-Nibble = Beschriftungs-Position (N, NO, O usw.)
POINTS CityPos;
POINTC PolyDiff[];
}MAPDATA;
// Info für Mercatorprojektion (der Ressourcen-Polygone)
const FLOAT Aequator=2105; // Pixel-Position
const FLOAT Nullmeridian=10; // Pixel-Position
const FLOAT PixPerGrad=22.78f; // Pixel pro Grad
// Berechnet Integral von 0 bis x über dt/cosh(t), im Gradmaß, liefert Gradmaß
// Zur Umrechnung von Karten-"Grad" in Breitengrad
static FLOAT gudermann(FLOAT x) {
return (FLOAT)((2*atan(exp(x*PI180))-PI/2)/PI180);
}
static PointF UnmapMercator(PointF pt) { // Umrechnung
pt.X = (pt.X-Nullmeridian)/PixPerGrad;
pt.Y = gudermann((Aequator-pt.Y)/PixPerGrad);
return pt;
}
static PointF MapMercator(PointF pt) {
pt.X = Nullmeridian+pt.X*PixPerGrad;
pt.Y = (float)(Aequator-log(tan(PI/4 + pt.Y*PI180/2))/PI180*PixPerGrad);
return pt;
}
static const FLOAT GradnetzX=5;
static const FLOAT GradnetzY=5; // alle 5 Grad eine Gerade / ein Kreis
// Gradnetz für Mercatorprojektion zeichnen (nur Geraden)
// Für Europa sinnvoller Bereich: x=-40°..90°, y=30°..80°
// DCF77-Empfangsgebiet: x=-10°..30°, y=35°..65°
// Geoposition des Senders: x=50°01', y=9°00'
// TODO: Entfernungsberechnung und Ausgabe (km/mi/ms) irgendwo
static void DrawMercatorNetz(const KINFO*K, HDC dc) {
PointF tl={-180,80},br={180,0},mi={12,42};
WCHAR s[10];
FLOAT f;
int i;
tl=K->kMap(tl); br=K->kMap(br); mi=K->kMap(mi);
for (f=30; f<=80; f+=GradnetzY) {
PointF w={0,f};
w=K->kMap(w);
i=lrint(w.Y);
Line(dc,lrint(tl.X),i,lrint(br.X),i);
TextOutW(dc,lrint(mi.X),i+2,s,wnsprintfW(s,elemof(s),L"%d",lrint(f)));
}
for (f=-40; f<=90; f+=GradnetzX) {
PointF w={f,0};
w=K->kMap(w);
i=lrint(w.X);
Line(dc,i,lrint(tl.Y),i,lrint(br.Y));
TextOutW(dc,i+2,lrint(mi.Y),s,wnsprintfW(s,elemof(s),L"%d",lrint(f)));
}
}
/*********************************************************
** „eclipse-DCF77-Wetter.jpg“ (meteotime.com) **
*********************************************************/
// Umrechnung für Kegelprojektion der o.g. Datei (keine echte Kegelprojektion!!)
#define PolX 724.5f // Pol: Pixelposition
#define PolY -1248 // Pol: Pixelposition
#define GradKoeffX 0.8f // Grad pro Grad (Berührkreis theoretisch arcsin(0.8)=53°)
#define GradKoeffY 42.25f // Pixel pro Grad (Berührkreis ist dann wo?)
#define GradNull 10 // Senkrechter Meridian
#define GradPol 95.97f // !=90 für nicht ganz echte Kegelprojektionen
#define Tangens (1.2f/180) // Verzerr-Koeffizient, für echte Kegelprojektion PI/180
#define Beruehr 53 // Hier sind die Breitenkreise am engsten
// Aus Länge+Breite logische Pixelkoordinaten ausrechnen
static PointF MapKegel(PointF pt) {
double a = (pt.X-GradNull)*GradKoeffX*PI180; // Winkel in rad
pt.Y = (float)(GradPol-tan((pt.Y-Beruehr)*Tangens)/Tangens-Beruehr)*GradKoeffY; // Radius in Pixel
pt.X = PolX+(float)sin(a)*pt.Y;
pt.Y = PolY+(float)cos(a)*pt.Y;
return pt;
}
// Aus logischen Pixelkoordinaten Länge+Breite berechnen
static PointF UnmapKegel(PointF pt) {
double a,b;
pt.X-=PolX; pt.Y-=PolY;
a = atan2(pt.X,pt.Y)/PI180/GradKoeffX+GradNull;
b = GradPol-hypotf(pt.X,pt.Y)/GradKoeffY-Beruehr;
pt.Y = (float)atan(b*Tangens)/Tangens+Beruehr;
pt.X = (float)a;
return pt;
}
#undef Beruehr
#undef Tangens
#undef GradPol
#undef GradKoeffY
#undef GradKoeffX
/*****************************************
** „Europa.gif“ (wikimedia.org) **
** „Europe_topography_map.png“ **
*****************************************/
#define EuPolX 612
#define EuPolY -950
#define EuKoeffX 0.79f // Grad pro Grad (Berührkreis theoretisch arcsin(0.8)=53°)
#define EuKoeffY 30.52f // Pixel pro Grad (Berührkreis ist dann wo?)
#define EuNull 15 // Senkrechter Meridian
#define EuPol 97.1f // !=90 für nicht ganz echte Kegelprojektionen
#define EuTangens (PI/180) // Verzerr-Koeffizient, für echte Kegelprojektion PI/180
#define EuBeruehr 52 // Hier sind die Breitenkreise am engsten
#define EtCropX 24 // hier wurde Europa.gif herausgeschnitten (?)
#define EtCropY 223
static PointF MapEu(PointF pt) {
double a = (pt.X-EuNull)*EuKoeffX*PI180; // Winkel in rad
pt.Y = (float)(EuPol-tan((pt.Y-EuBeruehr)*EuTangens)/EuTangens-EuBeruehr)*EuKoeffY; // Radius in Pixel
pt.X = EuPolX+(float)sin(a)*pt.Y;
pt.Y = EuPolY+(float)cos(a)*pt.Y;
return pt;
}
static PointF MapEt(PointF pt) {
pt=MapEu(pt);
pt.X+=EtCropX;
pt.Y+=EtCropY;
return pt;
}
// Aus logischen Pixelkoordinaten Länge+Breite berechnen
static PointF UnmapEu(PointF pt) {
double a,b;
pt.X-=EuPolX; pt.Y-=EuPolY;
a = atan2(pt.X,pt.Y)/PI180/EuKoeffX+EuNull;
b = EuPol-hypotf(pt.X,pt.Y)/EuKoeffY-EuBeruehr;
pt.Y = (float)(atan(b*EuTangens)/EuTangens+EuBeruehr);
pt.X = (float)a;
return pt;
}
static PointF UnmapEt(PointF pt) {
pt.X-=EtCropX;
pt.Y-=EtCropY;
return UnmapEu(pt);
}
#undef EuKoeffX
#undef EuKoeffY
#undef EuPol
#undef EuTangens
#undef EuBeruehr
static void DrawGradnetz(const KINFO*K, HDC dc);
static const KINFO KINFO_ECLIPSE={T("eclipse-DCF77-Wetter.jpg"), 1148,1290,MapKegel,UnmapKegel,DrawGradnetz};
static const KINFO KINFO_EUROPA ={T("Europa.gif"), 957, 910,MapEu, UnmapEu, DrawGradnetz};
static const KINFO KINFO_EUROPE ={T("Europe_topography_map.png"),1475,1200,MapEt, UnmapEt, DrawGradnetz};
static const KINFO KINFO_NONE ={NULL,0,0,MapMercator,UnmapMercator,DrawMercatorNetz};
static void DrawGradnetz(const KINFO*K, HDC dc) {
FLOAT r,lng;
PointF pol={PolX,PolY};
WCHAR s[10];
if (K!=&KINFO_ECLIPSE) {pol.X=EuPolX; pol.Y=EuPolY;}
if (K==&KINFO_EUROPE) {pol.X+=EtCropX; pol.Y+=EtCropY;}
for (r=30; r<=80; r+=GradnetzY) {
FLOAT rr;
PointF pt={GradNull,r};
if (K!=&KINFO_ECLIPSE) pt.X=EuNull;
pt=K->kMap(pt);
rr=pt.Y-pol.Y;
Ellipse(dc,lrint(pol.X-rr),lrint(pol.Y-rr),lrint(pol.X+rr),lrint(pol.Y+rr));
TextOutW(dc,lrint(pt.X)+2,lrint(pt.Y),s,wnsprintfW(s,elemof(s),L"%d",lrint(r)));
}
for (lng=-40; lng<=90; lng+=GradnetzX) {
PointF pp={lng,0};
pp=K->kMap(pp);
Line(dc,lrint(pol.X),lrint(pol.Y),lrint(pp.X),lrint(pp.Y));
pp.X=lng;
pp.Y=42;
pp=K->kMap(pp);
TextOutW(dc,lrint(pp.X)+1,lrint(pp.Y),s,wnsprintfW(s,elemof(s),L"%d",lrint(lng)));
if (lng==10) SetTextAlign(dc,TA_LEFT|TA_BOTTOM);
}
}
#undef EuPolX
#undef EuPolY
#undef GradNull
#undef PolX
#undef PolY
#undef EuNull
#undef EtCropX
#undef EtCropY
/*****************************************
** Ende kartenspezifischer Routinen **
*****************************************/
// MapExtent-Breite bzw. Höhe
static int MapExtentExt(const karte *K, int nBar) {
return (&K->MapExtent.right)[nBar]-(&K->MapExtent.left)[nBar];
}
// ScrollRect-Breite bzw. Höhe
static int ScrollRectExt(const karte *K, int nBar) {
return (&K->ScrollRect.right)[nBar]-(&K->ScrollRect.left)[nBar];
}
// Bitmap-Position skalieren
static int scale(const karte *K, int p) {
return MulDiv(p,K->Scale.Z,K->Scale.N);
}
// Client-Pixel-Verschiebung
static int shift(const karte *K, int nBar) {
return (&K->ScrollRect.left)[nBar]-K->ScrollInfo[nBar].nPos;
}
// Bitmap-Position nach Client-Position
static int bp2cp(const karte *K, int p, int nBar) {
return scale(K,p)+shift(K,nBar);
}
// Client-Position nach Bitmap-Position
static int cp2bp(const karte *K, int p, int nBar) {
return MulDiv(p-shift(K,nBar),K->Scale.N,K->Scale.Z);
}
// Gleitkomma-Version
static float cp2bpf(const karte *K, int p, int nBar) {
return (float)(p-shift(K,nBar))*K->Scale.N/K->Scale.Z;
}
// Mercator-Koordinate in Pixel-Koordinate (also 2x) umrechnen
static POINT MapCoord(const karte *K,const POINT *pt) { // Umrechnung
POINT ret=*pt;
if (K->kInfo->name) { // sonst Mercatorprojektion belassen
PointF pf={(FLOAT)pt->x,(FLOAT)pt->y};
pf=K->kInfo->kMap(UnmapMercator(pf));
ret.x=lrint(pf.X);
ret.y=lrint(pf.Y);
}
return ret;
}
#ifdef POLYEDIT
// Pixel-Koordinate in Mercator-Koordinate (also 2x) umrechnen
static POINT UnmapCoord(const karte *K,const POINT *pt) {
POINT ret=*pt;
if (K->kInfo->name) { // sonst Mercatorprojektion belassen
PointF pf={(FLOAT)pt->x,(FLOAT)pt->y};
pf=MapMercator(K->kInfo->kUnmap(pf));
ret.x=lrint(pf.X);
ret.y=lrint(pf.Y);
}
return ret;
}
TCHAR saveFileName[]=T("map3.rc");
POINT saveLast,savePrev;
DWORD saveLastPos,saveCountPos;
int saveCount;
static HANDLE saveOpen() {
HANDLE ret=CreateFile(saveFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,0,0);
if (ret==INVALID_HANDLE_VALUE) ret=0;
if (ret) SetFilePointer(ret,0,NULL,FILE_END);
return ret;
}
static void savePoint(const karte*K) {
HANDLE h=saveOpen();
if (h) {
HDC dc;
char s[64];
DWORD len;
POINT p={cp2bp(K,K->MousePos.x,SB_HORZ),cp2bp(K,K->MousePos.y,SB_VERT)};
p=UnmapCoord(K,&p);
if (!saveCount) {
saveCountPos=SetFilePointer(h,0,NULL,FILE_CURRENT);
len=wsprintfA(s," len,0x<country>,%d,%d\t// \n\t",p.x,p.y);
}else{
POINTC diff={(char)(p.x-saveLast.x),(char)(p.y-saveLast.y)};
len=wsprintfA(s,"0x%04X,",*(WORD*)&diff);
}
savePrev=saveLast;
saveLastPos=SetFilePointer(h,0,NULL,FILE_CURRENT);
saveLast=p;
if (!(++saveCount&7)) {s[len-1]='\n'; s[len++]=' ';};
WriteFile(h,s,len,&len,NULL);
CloseHandle(h);
p=MapCoord(K,&p);
p.x=bp2cp(K,p.x,SB_HORZ);
p.y=bp2cp(K,p.y,SB_VERT);
dc=GetDC(K->hMap);
Ellipse(dc,p.x-1,p.y-2,p.x+2,p.y+2); // Kontrollausgabe
ReleaseDC(K->hMap,dc);
}
}
static void saveUndo(const karte*K) {
if (saveLastPos) {
HANDLE h=saveOpen();
if (h) {
HDC dc;
POINT p;
SetFilePointer(h,saveLastPos,NULL,FILE_BEGIN);
SetEndOfFile(h);
CloseHandle(h);
saveLast=savePrev;
saveCount--;
saveLastPos=0; // Kein weiteres Undo!
p=MapCoord(K,&saveLast);
p.x=bp2cp(K,p.x,SB_HORZ);
p.y=bp2cp(K,p.y,SB_VERT);
dc=GetDC(K->hMap);
Line(dc,p.x-2,p.y-2,p.x+3,p.y+3); // durchkreuzen
Line(dc,p.x-2,p.y+3,p.x+3,p.y-2);
ReleaseDC(K->hMap,dc);
}
}
}
static void saveEnd(const karte*K) {
HANDLE h=saveOpen();
if (h) {
char s[64];
DWORD len;
len=wsprintfA(s,"\n\n");
WriteFile(h,s,len,&len,NULL);
len=wsprintfA(s,"%4d",saveCount);
saveCount=0;
SetFilePointer(h,saveCountPos,NULL,FILE_BEGIN);
WriteFile(h,s,len,&len,NULL);
CloseHandle(h);
MessageBeep(MB_OK);
saveLastPos=0; // kein Undo!
}
}
#endif
// Erzeuge Geokoordinaten-String (mit je 2 Kommastellen) aus Mausposition
static int MkGeoStr(const karte*K, TCHAR*buf, int len) {
PointF geo;
TCHAR sx[8],sy[8],rs[32],*px,*py;
geo.X=cp2bpf(K,K->MousePos.x,SB_HORZ);
geo.Y=cp2bpf(K,K->MousePos.y,SB_VERT);
geo=K->kInfo->kUnmap(geo);
FloatToStr((float)fabs(geo.X),2,sx,elemof(sx));
FloatToStr((float)fabs(geo.Y),2,sy,elemof(sy));
LoadString(ghInstance,66,rs,elemof(rs)); // "Geo: %c%s° / %c%s°\0NSOW"
py=rs+lstrlen(rs)+1; // auf "N"
px=py+2+(geo.X<0); // auf "O" (englisch "E") oder "W"
py+=geo.Y<0; // auf "S"
return wnsprintf(buf,len,rs,*py,sy,*px,sx);
}
/***************
* GDI-Krempel *
***************/
static struct{
HFONT f[4]; // 0..3: Gradnetz, Städte
HBRUSH b[11]; // Zuordnung siehe unten
HPEN p[7]; // Zuordnung siehe unten
}gdi;
static int gdiusage; // zählt die Fenster mit
static void CreateGdiHandles() {
CreateSymbGdiHandles(); // --> DlgWetter.c
if (!gdiusage) {
static const char sizes[elemof(gdi.f)]={6,8,10,12};
static const COLORREF brushcolors[elemof(gdi.b)]={
0x111111, // grau (TODO: Abschattung Miniaturfenster)
0x4444FF, // knallrot (mit Transparenz)
0xCCCCEE, // ein Index, wird transparent-rot
0xCCCCFF,
0xEECCCC, // Blau-Index
0xFFCCCC,
RGB(192,255,255), // Meer (blau)
RGB(128,255,128), // Land (hellgrün)
RGB(255,192,128), // Land (orange)
RGB(255,255,128), // Land (dunkelgelb)
RGB(255,160,192)}; // Land (rosarot)
LOGBRUSH lbr={BS_SOLID,0x111111};
int i;
HDC dc=GetDC(0);
int lpy=GetDeviceCaps(dc,LOGPIXELSY); // Schriftgröße wie aus dem Lehrbuch
ReleaseDC(0,dc);
for(i=0; i<elemof(gdi.f); i++) {
// Die Art der Transparenzerzeugung (hier: Gradnetzbeschriftung) beißt sich mit Antialiasing
gdi.f[i]=CreateFont(-MulDiv(sizes[i],lpy,72),0,0,0,FW_BOLD,0,0,0,0,0,0,
NONANTIALIASED_QUALITY|PROOF_QUALITY,FF_SWISS|VARIABLE_PITCH,NULL);
}
for(i=0; i<elemof(gdi.b); i++) {
gdi.b[i]=CreateSolidBrush(brushcolors[i]);
}
gdi.p[0]=CreatePen(PS_SOLID,1,0x111111); // normaler grauer Rand
gdi.p[1]=ExtCreatePen(PS_GEOMETRIC|PS_SOLID|PS_JOIN_BEVEL,3,&lbr,0,NULL); // dicker grauer Rand für "gehoverte" Region
lbr.lbColor=RGB(128,128,255);
gdi.p[2]=ExtCreatePen(PS_GEOMETRIC|PS_SOLID|PS_JOIN_BEVEL,3,&lbr,0,NULL); // dicker blauer Rand für Config.Region
gdi.p[3]=CreatePen(PS_SOLID,1,0x771111); // dunkelblau-transparent
gdi.p[4]=CreatePen(PS_SOLID,0,RGB(255,0,0)); // knallrot, für Miniaturfenster
lbr.lbColor=RGB(64,64,64);
gdi.p[5]=ExtCreatePen(PS_GEOMETRIC|PS_SOLID|PS_JOIN_ROUND,1,&lbr,0,NULL); // Rand der Landmasse
gdi.p[6]=CreatePen(PS_DOT,0,lbr.lbColor); // Innengrenzen
}
gdiusage++;
}
static void DeleteGdiHandles() {
if (!--gdiusage) {
int i;
for (i=0; i<sizeof(gdi)/sizeof(HGDIOBJ); i++) DeleteObject(((HGDIOBJ*)&gdi)[i]);
}
DeleteSymbGdiHandles();
}
static bool IsBitmapVisible(const karte *K) {
return K->kInfo && K->kInfo->name && K->kVis&1;
}
// Nur den Weißraum um die Karte herum löschen, um Flackern zu vermeiden
static void EraseOutside(const karte *K, HDC dc) {
SaveDC(dc);
if (IsBitmapVisible(K)) ExcludeClipRect(dc,
bp2cp(K,0,SB_HORZ),
bp2cp(K,0,SB_VERT),
bp2cp(K,K->kInfo->szBitmap.cx,SB_HORZ),
bp2cp(K,K->kInfo->szBitmap.cy,SB_VERT));
else if (K->kVis&0x08) SelectBrush(dc,gdi.b[6]); // Meeresfarbe, sonst weiß
PatBlt(dc,
K->ScrollRect.left,
K->ScrollRect.top,
ScrollRectExt(K,SB_HORZ),
ScrollRectExt(K,SB_VERT),
PATCOPY);
RestoreDC(dc,-1);
}
/*********************************
* Suchen von Vorhersageregionen *
*********************************/
static void CalcRects1(const karte *K, MAPMEM *m) {
int d;
RECT rc;
HRGN rgn=CreatePolygonRgn(m->Poly,m->nPoly,ALTERNATE);
GetRgnBox(rgn,&rc);
DeleteRgn(rgn);
m->Area=abs((int)CalcArea(m->Poly,m->nPoly,&m->Centroid));
InflateRect(&rc,3,3); // Zugabe für die breiten Linien und spitzen Ecken
SetRect(&m->rcPoly,
scale(K,rc.left),
scale(K,rc.top),
scale(K,rc.right),
scale(K,rc.bottom));
d=(m->CitySize>>1)+(K->kScale>>3)+1; // Größe logarithmisch der Skalierung (etwas) anpassen
SETMAX(d,2); // Minimalen Durchmesser (in Client-Pixel) garantieren (GM_ADVANCED: 3 Pixel)
SetRectC(&m->rcCity,scale(K,m->CityPos.x),scale(K,m->CityPos.y),d,d);
}
static void CalcRects(const karte *K) {
int i;
for (i=0; i<=90; i++) CalcRects1(K,K->map[i]);
}
// Invalidiert ein Rechteck, gegeben in unverschobenen Client-Pixeln
static void InvalRect(const karte *K, const RECT *rc) {
RECT r;
CopyRect(&r,rc);
OffsetRect(&r,shift(K,SB_HORZ),shift(K,SB_VERT));
#ifdef _DEBUG
{HDC dc=GetDC(K->hMap);
Rectangle(dc,r.left,r.top,r.right,r.bottom);
Line(dc,r.left,r.top,r.right-1,r.bottom-1);
Line(dc,r.left,r.bottom-1,r.right-1,r.top);
ReleaseDC(K->hMap,dc);
}
#endif
InvalidateRect(K->hMap,&r,TRUE);
}
// Invalidiert einen Polygonbereich, bei k==90 den eigenen Standort
static void InvalIndex(const karte *K, int k) {
if ((unsigned)k<=90) InvalRect(K,&K->map[k]->rcPoly);
}
// Kümmert sich um das (begrenzte) Invalidieren
static void InvalHoverIndex(const karte *K) {
InvalIndex(K,K->HoverIndex);
}
// Stadt-Name, ggf. mit Nummer
static int GenTextW(const karte *K, int k, PWSTR s, int len) {
if (K->kVis&0x80) { // Stadt-Name?
int sl,rl; // String-Länge, Ressourcen-Länge
WCHAR r[64],*rp=r;
rl=MyLoadStringW(256+k,r,elemof(r));
sl=lstrlenW(r);
if (sl<rl) { // tatsächlich zweiteilig?
rp+=++sl; // Zeiger auf die Stadt setzen
sl=rl-sl; // Stringlänge korrigieren
}
if (K->kVis&0x40) { // Nummer dazu gewünscht?
return wnsprintfW(s,len,L"%d:%s",k,rp); // TEST WIN98!!
}else{
StrCpyNW(s,rp,len);
return sl;
}
}else return wnsprintfW(s,len,L"%d",k); // nur die Nummer
}
// Ermittelt Text-Rechteck je nach Alignment
// Problem: Wo ist TA_BASELINE?
static void GetTextRectW(HDC dc, PCWSTR s, int len, const POINT *org, RECT *rc) {
int a=GetTextAlign(dc);
SIZE sz;
GetTextExtentPoint32W(dc,s,len,&sz);
if (a&TA_UPDATECP) GetCurrentPositionEx(dc,(POINT*)rc);
else *(POINT*)rc=*org;
if (a&TA_RIGHT) rc->left-=a&(TA_CENTER&~TA_RIGHT)?sz.cx>>1:sz.cx;
rc->right=rc->left+sz.cx;
if (a&TA_BOTTOM) rc->top-=a&(TA_BASELINE&~TA_BOTTOM)?MulDiv(sz.cy,5,6):sz.cy;
rc->bottom=rc->top+sz.cy;
}
// bewegliches l,r gegen festes L,R, liefert vorgeschlagene Verschiebung, 0 wenn keine Überlappung
static int intersect(int l, int r, int L, int R) {
if (l+r<L+R) { // die Mitten legen das Vorzeichen des Verschiebungsvorschlags fest
if ((L-=r)<0) return L; // negative Zahl
}else{
if ((R-=l)>0) return R; // positive Zahl
}
return 0;
}
// bewegliches r gegen festes R (um ix,iy aufgeblasen) auf Überlappung prüfen
// Liefert jeweilige vorgeschlagende Fluchtrichtung von r
static U64 intersectR(const RECT*r, const RECT*R, int ix, int iy) {
U64 ret={0};
if (!IsRectEmpty(R)
&& (ret.x=intersect(r->left,r->right,R->left-ix,R->right+ix))
&& !(ret.y=intersect(r->top,r->bottom,R->top-iy,R->bottom+iy)))
ret.x=0; // X-Überlappung ungültig, wenn's keine Y-Überlappung gibt
return ret;
}
static U64 intersectRect(const karte *K, const RECT *rc, int kk) {
int k;
U64 ret;
for (k=0; k<=90; k++) {
const MAPMEM *m=K->map[k];
if (kk>=0) { // nur für Text, nicht für Wettersymbole
ret=intersectR(rc,&m->rcCity,kk!=k?2:0,kk!=k);
if (ret.ll) return ret;
}
if (m->fix&1) {
ret=intersectR(rc,&m->rcText,1,0);
if (ret.ll) return ret;
}
if (m->fix&2) {
ret=intersectR(rc,&m->rcSymb,0,0);
if (ret.ll) return ret;
}
}
return ret;
// if (kk==0) DbgPrintf(("%d überlappt %d\n",kk,k));
}
// bewegliches l,r innerhalb L,R zwingen (dafür Verchiebung ermitteln)
static bool moveinto(int l, int r, int L, int R, int*v) {
if (r-l>R-L) return false; // passt überhaupt nicht
*v=0;
if ((L-=l)>0) *v=L; // positiv
else if ((R-=r)<0) *v=R; // negativ
return true;
}
// liefert true wenn's (nach Verschiebung) komplett drin liegt
// hv Bit 0: Erlaube horizontale Verschiebung, Bit 1: dito vertikal
static bool moveRectIntoRect(RECT*r, const RECT*R, BYTE hv) {
int dx,dy;
if (!moveinto(r->left,r->right,R->left,R->right,&dx)) return false;
if (dx && !(hv&1)) return false;
if (!moveinto(r->top,r->bottom,R->top,R->bottom,&dy)) return false;
if (dy && !(hv&2)) return false;
OffsetRect(r,dx,dy);
return true;
}
static void GenTextRect(const karte *K, HDC dc, int k) {
if (k>=0) {
/*
static const BYTE textalign[8]={
TA_BOTTOM|TA_CENTER,
TA_BOTTOM|TA_LEFT,
TA_BASELINE|TA_LEFT,
TA_TOP|TA_LEFT,
TA_TOP|TA_CENTER,
TA_TOP|TA_RIGHT,
TA_BASELINE|TA_RIGHT,
TA_BOTTOM|TA_RIGHT};*/
MAPMEM *m=K->map[k];
RECT rc;
RECT vis;
//int pos,dia;
POINT pt={(m->rcCity.left+m->rcCity.right)>>1,m->rcCity.bottom};
U64 fl; // Fluchtweg
//POINT ofs;
WCHAR s[64];
if (m->fix&1) return; // schon fixiert!
//pos=m->TextPos; // Position der Beschriftung (0..7)
//dia=m->CitySize;
//if (dia<4) dia=4;
CopyRect(&vis,&K->ScrollRect);
OffsetRect(&vis,-shift(K,SB_HORZ),-shift(K,SB_VERT));
if (IntersectRect(&rc,&m->rcCity,&vis)) { // Stadt überhaupt sichtbar?
SetTextAlign(dc,TA_CENTER|TA_TOP);
//ofs=sincos(dia>>1,pos*450-900); // Schrift-Ausgangspunkt etwas wegsetzen
//if (pos==2||pos==6) ofs.y+=4; // etwa auf vertikale Textmitte setzen (da fehlt TA_VCENTER)
//pt.x=scale(K,pt.x);
//pt.y=scale(K,pt.y);
//pt.x+=ofs.x;
//pt.y+=ofs.y;
GetTextRectW(dc,s,GenTextW(K,k,s,elemof(s)),&pt,&rc);
if (!moveRectIntoRect(&rc,&vis,1)) goto tryupper;
if ((fl=intersectRect(K,&rc,k)).ll) {
OffsetRect(&rc,fl.x,0); // probiere (nur) seitliche Verschiebung
if (rc.right<pt.x || rc.left>pt.x // zu viel geschoben
|| intersectRect(K,&rc,k).ll
|| !moveRectIntoRect(&rc,&vis,0)) {
tryupper:
// Weitere Anordnungen versuchen
pt.y=m->rcCity.top;
SetTextAlign(dc,TA_CENTER|TA_BOTTOM); // oben darüber
GetTextRectW(dc,s,GenTextW(K,k,s,elemof(s)),&pt,&rc); // Quatsch, da muss es was besseres geben!
if (!moveRectIntoRect(&rc,&vis,1)) goto leer;
if ((fl=intersectRect(K,&rc,k)).ll) {
OffsetRect(&rc,fl.x,0); // probiere (nur) seitliche Verschiebung
if (rc.right<pt.x || rc.left>pt.x // zu viel geschoben
|| intersectRect(K,&rc,k).ll
|| !moveRectIntoRect(&rc,&vis,0)) goto leer;
}
}
}
}else leer: SetRectEmpty(&rc);
if (!EqualRect(&m->rcText,&rc)) {
InvalRect(K,&m->rcText);
InvalRect(K,&rc);
CopyRect(&m->rcText,&rc);
}
m->fix|=1; // fixieren
}
}
// 1. Parameter: Tag (Bits 5:4)
// 2. Parameter: Region (0..89)
static U64 GetTagWetter(BYTE kWetter, int k) {
U64 wetter[4];
GetForecast(k,wetter); // gehe zu DlgWetter und greife in den Cache24
return wetter[kWetter>>4&3]; // wähle Tag 0..3
}
static void GenWetterRect(const karte*K, HDC dc, int k, int kinval) {
if ((unsigned)k<90) {
RECT alt,neu;
POINT mi;
MAPMEM *m=K->map[k];
bool repaint=false;
if (m->fix&2) return; // war schon mal dran: nichts tun!
CopyRect(&alt,&m->rcSymb);
CopyRect(&neu,&alt);
mi.x=scale(K,m->Centroid.x);
mi.y=scale(K,m->Centroid.y);
if (kinval<0 || kinval==k) {
U64 v=GetTagWetter(K->kWetter,k); // Wetter des gewünschten Tages
Measure(dc,v,(BYTE)(K->kWetter&0x0F),&neu);
if (!IsRectEmpty(&neu)) OffsetRect(&neu,mi.x,mi.y);
repaint=true;
}
if (!EqualRect(&alt,&neu)) {
InvalRect(K,&alt);
CopyRect(&m->rcSymb,&neu);
repaint=true;
}
if (repaint) InvalRect(K,&neu);
m->fix|=2;
}
}
static void WetterDC(const karte*K, HDC dc) {
static const char scales[4]={24,32,48,64};
int sc=scales[K->kAutoScale>>6]; // Größe festlegen
SetMapMode(dc,MM_ANISOTROPIC);
SetWindowExtEx(dc,64,64,NULL);
SetViewportExtEx(dc,sc,sc,NULL);
}
// Verteilt Wettersymbole und Städte-Namen auf Nicht-Überlappung
// newWetter <0: Alle Wettersymbole
// newWetter = 0..89: Ein Wettersymbol
// newWetter >= 90: Kein Wettersymbol ist neu (könnte aber neu platziert werden)
static void InvalSymbChange(const karte *K, int newWetter) {
int k,i;
HDC dc=GetDC(K->hMap); // hier wird nichts gezeichnet!
WetterDC(K,dc);
for (i=0; i<=90; i++) K->map[i]->fix=0; // Fixierungs-Bits löschen
GenWetterRect(K,dc,newWetter,newWetter); // absolut bevorzugt
GenWetterRect(K,dc,K->HoverIndex,newWetter);
GenWetterRect(K,dc,Config.Region,newWetter);
for (i=0; i<90; i++) GenWetterRect(K,dc,i,newWetter);
SetMapMode(dc,MM_TEXT);
SelectFont(dc,gdi.f[K->kAutoScale>>4&3]);
GenTextRect(K,dc,K->HoverIndex);
GenTextRect(K,dc,Config.Region);
if (Config.uRegion>=0) GenTextRect(K,dc,90);
for (i=0x0F; i>=0; i--) { // von großen zu kleinen Nestern
for (k=0; k<90; k++) { // jeweils durchgehen
if (K->map[k]->CitySize==i) GenTextRect(K,dc,k);
}
}
ReleaseDC(K->hMap,dc);
//#ifdef _DEBUG
// for (i=0; i<=90; i++) dprintf("%d",!IsRectEmpty(&K->map[i]->rcText));
// dprintf("\n");
//#endif
}
/********************
* Variablen setzen *
********************/
// Ermittelt das Fenster-Handle der Wetter-Eigenschaftsseite,
// Null wenn nicht im Vordergrund; für's Hot-Tracking der Wettervorhersage
static HWND GetPropWetter() {
if (PropWnd) {
HWND PageWnd=PropSheet_GetCurrentPageHwnd(PropWnd);
if (PageWnd && PropSheet_HwndToIndex(PropWnd,PageWnd)==7) return PageWnd;
} // Der Index 7 stimmt hier immer
return 0;
}
static void SetShowRegion(char value) {
HWND hWetter=GetPropWetter();
if (hWetter) SetCurRegion(hWetter,value);
}
static void SetRegion(char r) {
if (r<0) return;
InfoPropSheet(18,Config.Region);
Config.Region=r;
SetShowRegion(r);
}
static void PropShowWetter(void) {
ShowProperties();
PropSheet_SetCurSel(PropWnd,0,7);
}
// Sucht euklidisch nächstgelegene Vorhersage-Stadt,
// bezogen auf die aktuell verwendete Projektion
// Liefert immer 0 <= Wert < 90
static int FindNearestCity(const karte *K, int x, int y) {
int i=0;
int r=-1; // sollte nicht dabei bleiben
DWORD d2min=(DWORD)-1;
for (i=0; i<90; i++) {
MAPMEM *m=K->map[i];
if (m->nPoly) {
int dx=x-m->CityPos.x;
int dy=y-m->CityPos.y;
DWORD d2=dx*dx+dy*dy; // Abstandsquadrat; aufs Wurzelziehen wird verzichtet
if (d2min>d2) d2min=d2, r=i; // Minimum merken
}
}
return r;
}
static void OffsetPoints(POINT*pt, int len, int dx, int dy) {
int i;
for (i=0; i<len; i++) {
pt[i].x+=dx;
pt[i].y+=dy;
}
}
static int SearchStaat(const karte *K, int x, int y) {
int i;
for (i=0; i<NUMSTAAT; i++) {
HRGN rgn=K->staat[i];
if (rgn && PtInRegion(rgn,x,y)) return i;
}
return -1;
}
/*******************
* Zeichenroutinen *
*******************/
// Berechnet eigenen Standort in K->map[90] und generiert ein "X" als Zwölfeck
static void CalcMyPos(karte *K, bool full) {
int i;
MAPMEM *m=K->map[90];
POINT pt={Config.uPos.x,Config.uPos.y};
m->CityPos=MapCoord(K,&pt);
K->MyPosStaat=SearchStaat(K,m->CityPos.x,m->CityPos.y);
if (full) {
m->nPoly=12;
for (i=0; i<12; i++) m->Poly[i]=sincos(i%3?10:4,i*300); // ein "X" erzeugen
OffsetPoints(m->Poly,12,m->CityPos.x,m->CityPos.y);
for (i=0; i<12; i++) InflateRectForPoint(&K->MapExtent,&m->Poly[i]);
}
//m->TextPos=4; // unten
}
// Map-Mode: MM_TEXT mit ViewportOrg
__forceinline void DrawCityName(karte *K, HDC dc, int k) {
MAPMEM *m=K->map[k];
WCHAR s[64];
if (IsRectEmpty(&m->rcText)) return;
#if 0//def _DEBUG
SaveDC(dc);
SelectPen(dc,GetStockPen(BLACK_PEN));
SelectBrush(dc,GetStockBrush(HOLLOW_BRUSH));
SetGraphicsMode(dc,GM_COMPATIBLE);
{RECT *r=&m->rcText;
Rectangle(dc,r->left,r->top,r->right,r->bottom);
}
RestoreDC(dc,-1);
#endif
TextOutW(dc,m->rcText.left,m->rcText.top,s,GenTextW(K,k,s,elemof(s)));
}
static void DrawCityNames(karte *K, HDC dc) {
int i,k;
SetTextAlign(dc,TA_TOP|TA_LEFT);
k=90+(Config.uRegion>=0);
for (i=0; i<k; i++) DrawCityName(K,dc,i);
}
static bool ManipulateDIBits(HDC hdc, HBITMAP hBmp, void(*fn)(COLORREF*,int)) {
// Bitmap aus DC extrahieren
BITMAP bm;
BITMAPINFOHEADER bi;
COLORREF *bits;
int k;
bool ret=false;
GetObject(hBmp,sizeof(bm),&bm);
InitStruct(&bi,sizeof(bi));
bi.biWidth = bm.bmWidth;
bi.biHeight = bm.bmHeight;
bi.biPlanes = bm.bmPlanes; // 1
bi.biBitCount = bm.bmBitsPixel; // 32
k=bm.bmWidth*bm.bmHeight; // Größe in DWORDs
bits=LocalAlloc(LMEM_FIXED,k*sizeof(COLORREF));
if (bits) {
if (GetDIBits(hdc,hBmp,0,bm.bmHeight,(void**)bits,(BITMAPINFO*)&bi,DIB_RGB_COLORS)) {
// Pixel bearbeiten
fn(bits,k);
// Bitmap zurücktransferieren
if (SetDIBits(hdc,hBmp,0,bm.bmHeight,(void**)bits,(BITMAPINFO*)&bi,DIB_RGB_COLORS)) ret=true;
}
LocalFree(bits);
}
return ret;
}
static void AllTransparent(COLORREF *cr, int k) {
__stosd(cr,0xFF000000,k);
}
/* PROBLEM: Windows-Stifte und -Pinsel haben immer T=0 (oberstes Byte),
* da lässt sich Windows offenbar nicht austricksen.
* Deshalb muss hier nachträglich - je nach Farbe - der Transparenzwert angepasst werden
* Nicht überzeichnete Pixel behalten den bei TransAll() gesetzten Pixelwert.
* Achtung! Es sind BGR-Werte!
*/
static void ReplaceColors(COLORREF *cr, int k) {
int i;
for (i=0; i<k; i++, cr++) {
switch (*cr) {
// Polygonfarben:
case 0xCCCCEE: *cr=RGBA(255,192,192,64); break; // rot, glasig
case 0xCCCCFF: *cr=RGBA(255,192,192,128); break; // dito, weniger transparent
case 0xEECCCC: *cr=RGBA(192,192,255,64); break; // blau, glasig
case 0xFFCCCC: *cr=RGBA(192,192,255,128); break;
case 0x111111: *cr=RGBA(0,0,0,128); break; // schwarzer Rand (erscheint grau)
// Gradnetz-Farbe:
case 0x111177: *cr=RGBA(128,0,0,160); break; // dunkelblau, transparent
case 0x771111: *cr=RGBA(0,0,128,160); break;
// Stadtklecks-Farbe:
case 0x4444FF: *cr=RGBA(255,0,0,192); break; // knallrot, etwas transparent
case 0xFF4444: *cr=RGBA(0,0,255,192); break;
// Landmasse-Polygonfarben:
case 0x80FF80: *cr=RGBA(0,255,0,64); break;
case 0xFFC080: *cr=RGBA(0,128,255,64); break;
case 0xFFFF80: *cr=RGBA(0,255,255,64); break;
case 0xFFA0C0: *cr=RGBA(128,0,255,64); break;
// Farbe behalten; nicht benutzte Pixel werden hier 100% transparent
default: *cr^=0xFF000000;
}
}
}
// Setzt Offset und Skalierung. Beim Offset geht die Scroll-Position bereits mit ein.
// dx und dy sind
// - der Aktualisierungsausschnitt beim Speichergerätekontext
// die linke obere Ecke
static void PrepareDC(const karte *K, HDC dc, int dx, int dy) {
SetGraphicsMode(dc,GM_ADVANCED);
SetBkMode(dc,TRANSPARENT);
SetViewportOrgEx(dc,shift(K,SB_HORZ)-dx,shift(K,SB_VERT)-dy,NULL);
}
static void ScaleDC(const karte*K, HDC dc) {
SetMapMode(dc,MM_ANISOTROPIC);
SetWindowExtEx(dc,K->Scale.N,K->Scale.N,NULL);
SetViewportExtEx(dc,K->Scale.Z,K->Scale.Z,NULL);
}
#define UnscaleDC(dc) SetMapMode((dc),MM_TEXT);
// dc muss MM_TEXT haben
// DoDraw wird mit unskaliertem, aber auf Bitmap-Ausschnitt verschobenen dc aufgerufen
static bool DrawTransparent(karte *K, HDC dc, RECT *rc, void(*DoDraw)(karte*,HDC)) {
bool ret=false;
#ifndef UNICODE
if (!NoAlpha)
#endif
{
SIZE sz={rc->right-rc->left,rc->bottom-rc->top};
HDC memdc=CreateCompatibleDC(dc);
HBITMAP membm=CreateCompatibleBitmap(dc,sz.cx,sz.cy);
if (membm) {
HBITMAP obm=SelectBitmap(memdc,membm);
const BLENDFUNCTION bf={AC_SRC_OVER,0,255,AC_SRC_ALPHA};
if (ManipulateDIBits(memdc,membm,AllTransparent)) {
POINT org;
GetViewportOrgEx(dc,&org);
SaveDC(memdc);
PrepareDC(K,memdc,rc->left,rc->top);
DoDraw(K,memdc);
RestoreDC(memdc,-1); // AlphaBlend() geht nur mit MM_TEXT sowie <memdc> ohne Offset
if (ManipulateDIBits(memdc,membm,ReplaceColors)
&& AlphaBlend(dc,rc->left-org.x,rc->top-org.y,sz.cx,sz.cy,memdc,0,0,sz.cx,sz.cy,bf)) ret=true;
} // AlphaBlend() funktioniert nicht unter Win98 in Nieschütz! Liefert 0x1D7.
// Funktioniert unter Win98 in Chemnitz, aber liefert 0x2F7. Pixelzeilenzahl?
SelectBitmap(memdc,obm);
DeleteBitmap(membm);
}
DeleteDC(memdc);
}
if (!ret) { // Wenn etwas schief ging … (Tobias' Win98-Rechner)
DoDraw(K,dc);
}
return ret;
}
static void DrawBorderLine(karte*K, HDC dc, const BORDEREX*bo, HPEN pen) {
SelectPen(dc,pen);
Polyline(dc,bo->points,bo->n);
#ifdef POLYEDIT
{HPEN open=SelectPen(dc,GetStockPen(BLACK_PEN));
int i;
for (i=0; i<bo->n; i++)
Ellipse(dc,bo->points[i].x,bo->points[i].y,bo->points[i].x+1,bo->points[i].y+1);
SelectPen(dc,open);
}
#endif
}
// Landmasse und Grenzen malen
static void DoDrawGrenzen(karte*K, HDC dc) {
int i;
const BORDEREX*bo;
ScaleDC(K,dc);
if (K->kVis&0x08) // Landmasse?
for (i=0; i<NUMSTAAT; i++)
FillRgn(dc,K->staat[i],gdi.b[7+Vierfarben[i]]);
if (K->kVis&0x04) // Landgrenzen?
for (bo=&K->bo; bo->valid; bo=IterateBorderex(bo))
if ((bo->staaten[0]|bo->staaten[1])>=0
&& bo->staaten[1]!=0x7F) // RUS+Kunstnordgrenze
DrawBorderLine(K,dc,bo,gdi.p[6]);
if (K->kVis&0x02) // Küstenlinie?
for (bo=&K->bo; bo->valid; bo=IterateBorderex(bo))
if ((bo->staaten[0]|bo->staaten[1])<0)
DrawBorderLine(K,dc,bo,gdi.p[5]);
UnscaleDC(dc);
}
// Gradnetz und Gradzahlen in Einheitsfarbe ausgeben
static void DoDrawGradnetz(karte*K, HDC dc) {
ScaleDC(K,dc);
SelectFont(dc,gdi.f[K->kAutoScale>>4&3]);
SelectPen(dc,gdi.p[3]);
SelectBrush(dc,GetStockBrush(HOLLOW_BRUSH));
SetTextColor(dc,0x771111); // Blau-Index
SetTextAlign(dc,TA_TOP|TA_LEFT);
K->kInfo->kGradnetz(K->kInfo,dc);
UnscaleDC(dc);
}
// Polygon malen
static void DoDrawPoly(karte*K, HDC dc, int k) {
int i=(k==90?Config.uRegion:k)<60?4:2;
if (k==K->HoverIndex) i++;
SelectBrush(dc,gdi.b[i]);
i&=1;
if (!i && k==Config.Region) i=2;
SelectPen(dc,gdi.p[i]);
Polygon(dc,K->map[k]->Poly,K->map[k]->nPoly); // zeichnet nichts für nPoly==0
}
// Polygon mit Vorbereitung malen
static void DoDrawPolyP(karte*K, HDC dc, int k) {
ScaleDC(K,dc);
DoDrawPoly(K,dc,k);
UnscaleDC(dc);
}
// Stadtklecks malen
static void DoDrawCity(const karte *K, HDC dc, int k) {
MAPMEM *m=K->map[k];
if (m->nPoly && m->CitySize) {
Ellipse(dc,m->rcCity.left,m->rcCity.top,m->rcCity.right,m->rcCity.bottom);
}
}
static void PrepareDrawCity(HDC dc) {
SelectBrush(dc,gdi.b[1]);
SelectPen(dc,GetStockPen(NULL_PEN));
}
// Stadtklecks malen, mit Vorbereitung von Pinsel und Stift
static void DoDrawCityP(const karte *K, HDC dc, int k) {
PrepareDrawCity(dc);
DoDrawCity(K,dc,k);
}
static void DoDrawPolys(karte*K, HDC dc) {
int k;
ScaleDC(K,dc);
for (k=0; k<=90; k++) {
if (k!=K->HoverIndex && k!=Config.Region) DoDrawPoly(K,dc,k);
}
UnscaleDC(dc);
if (K->kVis&0xC0) {
PrepareDrawCity(dc);
for (k=0; k<=90; k++) {
if (k!=K->HoverIndex && k!=Config.Region) DoDrawCity(K,dc,k);
}
}
k=Config.Region;
if (k>=0 && k!=K->HoverIndex) {
DoDrawPolyP(K,dc,k); // mit blauem Rand (SELECTED)
if (K->kVis&0xC0) DoDrawCityP(K,dc,k);
}
k=K->HoverIndex;
if (k>=0) {
DoDrawPolyP(K,dc,k); // mit weißem Rand zuletzt (HOVERED)
if (K->kVis&0xC0) DoDrawCityP(K,dc,k);
}
}
static void BltBitmap(HDC dc, HBITMAP hbm) {
HDC sdc; // Quell-DC
HBITMAP obm; // Original-Bitmap (1x1)
BITMAP bm; // um die Größe von hbm zu erfahren
GetObject(hbm,sizeof(bm),&bm);
sdc=CreateCompatibleDC(dc);
obm=SelectBitmap(sdc,hbm);
BitBlt(dc,0,0,bm.bmWidth,bm.bmHeight,sdc,0,0,SRCCOPY);
SelectBitmap(sdc,obm);
DeleteDC(sdc);
}
// Malt die Bitmap in den geeignet vorbereiteten Gerätekontext
static void PaintPicture(const karte *K, HDC dc) {
if (K->pPicture && K->kInfo->name) {
if (K->bPictureHBM) { // Gdiplus (ab XP oder bei vorhandener gdiplus.dll)
SetStretchBltMode(dc,HALFTONE);
SetBrushOrgEx(dc,-K->ScrollInfo[SB_HORZ].nPos,-K->ScrollInfo[SB_VERT].nPos,NULL);
BltBitmap(dc,K->hPicture);
}else{ // OLE
SIZE szHiMetric;
RECT rc;
(*(IPictureVtbl**)K->pPicture)->get_Width(K->pPicture,&szHiMetric.cx);
(*(IPictureVtbl**)K->pPicture)->get_Height(K->pPicture,&szHiMetric.cy);
SetRect(&rc,0,0,K->kInfo->szBitmap.cx,K->kInfo->szBitmap.cy);
(*(IPictureVtbl**)K->pPicture)->Render(K->pPicture,dc,
rc.left,rc.top,rc.right,rc.bottom,
0,szHiMetric.cy,szHiMetric.cx,-szHiMetric.cy,&rc);
}
}
}
static void DrawAll(karte *K, HDC dc, const RECT *rcInval) {
RECT rc;
int k;
CopyRect(&rc,&K->ScrollRect);
if (rcInval) CopyRect(&rc,rcInval);
PrepareDC(K,dc,0,0);
if (IsBitmapVisible(K)) {
ScaleDC(K,dc);
PaintPicture(K,dc);
UnscaleDC(dc);
}
if (K->kVis&0x0E) {
if (IsBitmapVisible(K)) DrawTransparent(K,dc,&rc,DoDrawGrenzen); // noch eine transparente Lage
else DoDrawGrenzen(K,dc); // ohne Bitmap: deckend
}
if (K->kVis&0x10) DrawTransparent(K,dc,&rc,DoDrawGradnetz); // 1. Lage
if (K->kVis&0x20) DrawTransparent(K,dc,&rc,DoDrawPolys); // 2. Lage, ggf. mit Stadt-Klecksen
// Alles weitere ist wieder deckend
if (K->kVis&0xC0) { // Städte, ohne Stadt-Kleckse, wenn bereits mit 2. Lage gezeichnet
int k;
PrepareDrawCity(dc); // hier: ohne Einhaltung einer Reihenfolge
for (k=0; k<=90; k++) DoDrawCity(K,dc,k);
}
if (K->kVis&0xC0) {
SelectFont(dc,gdi.f[K->kAutoScale>>4&3]);
DrawCityNames(K,dc);
}
if (K->kWetter&15) {
WetterDC(K,dc);
for (k=0; k<90; k++) {
U64 v=GetTagWetter(K->kWetter,k);
if (v.ull) {
POINT pt=K->map[k]->Centroid;
SetViewportOrgEx(dc,bp2cp(K,pt.x,SB_HORZ),bp2cp(K,pt.y,SB_VERT),NULL);
switch (K->kWetter&15) {
case 1: ptSymbolTag(dc,v); break;
case 2: if (v.Tag) ptTempTag(dc,v); break;
case 3: ptSymbolNacht(dc,v,K->kWetter>>4&3); break; // Tag 0..3 (wegen Mondphase)
case 4: if (v.Nacht) ptTempNacht(dc,v); break;
case 5: if (v.Tag) ptNSW(dc,v); break; // Niederschlagswahrscheinlichkeit
case 6: if (v.Nacht) ptWind(dc,v); break;
case 7: ptWarnung(dc,v); break;
}
}
}
}
#ifdef _DEBUG // Kontrollrechtecke ausgeben
UnscaleDC(dc);
SetWindowOrgEx(dc,0,0,NULL);
SetViewportOrgEx(dc,shift(K,SB_HORZ),shift(K,SB_VERT),NULL);
SetGraphicsMode(dc,GM_COMPATIBLE);
SelectPen(dc,GetStockPen(BLACK_PEN));
SelectBrush(dc,GetStockBrush(HOLLOW_BRUSH));
for (k=0; k<90; k++) {
RECT *r=&K->map[k]->rcText;
Rectangle(dc,r->left,r->top,r->right,r->bottom);
r=&K->map[k]->rcSymb;
Rectangle(dc,r->left,r->top,r->right,r->bottom);
}
#endif
}
/************************************************
* StatusXxx-Funktionen, je eine für jedes Feld *
************************************************/
// Statuszeile Feld 0: Skalierung
static void StatusScale(const karte *K) {
TCHAR s[32],t[32];
LoadString(ghInstance,65,t,elemof(t));
wnsprintf(s,elemof(s),t,MulDiv(100,K->Scale.Z,K->Scale.N));
SendMessage(K->hStatus,SB_SETTEXT,0,(LPARAM)s);
}
// Statuszeile Feld 1: Geoposition der Maus
static void StatusGeo(const karte *K) {
TCHAR s[64];
MkGeoStr(K,s,elemof(s));
SendMessage(K->hStatus,SB_SETTEXT,1,(LPARAM)s);
}
// Statuszeile Feld 2: Staat mit Flagge
static void StatusStaat(const karte *K) {
SendMessage(K->hStatus,SB_SETTEXT,2|SBT_OWNERDRAW,0);
}
#ifdef POLYEDIT
static void StatusMercator(const karte*K) {
POINT p;
TCHAR s[64];
p.x=cp2bp(K,K->MousePos.x,SB_HORZ);
p.y=cp2bp(K,K->MousePos.y,SB_VERT);
p=UnmapCoord(K,&p);
wnsprintf(s,elemof(s),T("%d/%d"),p.x,p.y);
SendMessage(K->hStatus,SB_SETTEXT,2,(LPARAM)s);
}
#endif
// Statuszeile Feld 3: Vorhersageregion mit Nummer
static void StatusRegion(const karte *K) {
TCHAR s[64];
s[0]=0; // Löschen wenn in keiner Region
if (K->HoverIndex>=0) {
TCHAR r[64],t[32];
LoadString(ghInstance,68,t,elemof(t));
LoadString(ghInstance,256+K->HoverIndex,r,elemof(r));
wnsprintf(s,elemof(s),t,K->HoverIndex,r);
}
SendMessage(K->hStatus,SB_SETTEXT,3,(LPARAM)s);
}
// Statuszeile Feld 4: Welches Wetter von welchem Tag (entsprechend Menüpunkt)
// ODER Schwerwetter-Anzeige
static void StatusWetter(karte *K) {
TCHAR s[128];
s[0]=0; // Löschen wenn keine Wetteranzeige
if (K->kWetter&7) {
if ((BYTE)K->HoverIndex<90) {
TCHAR st[64],sn[64];
BYTE b=0; // bool-Bits: Bit 0 = Tag-Warnung, Bit 1 = Nacht-Warnung
U64 v=GetTagWetter(K->kWetter,K->HoverIndex);
if ((BYTE)0xE6>>(K->kWetter&7)&1 && SchwerwetterText(st,elemof(st),false,v,true)) b|=1;
if ((BYTE)0xF8>>(K->kWetter&7)&1 && SchwerwetterText(sn,elemof(sn),true,v,true)) b|=2;
if (b) {
switch (b) {
case 1: lstrcpyn(K->strStatus4,st,elemof(K->strStatus4)); break; // nur Tag
case 2: lstrcpyn(K->strStatus4,sn,elemof(K->strStatus4)); break; // nur Nacht
case 3: wnsprintf(K->strStatus4,elemof(K->strStatus4),T("%s / %s"),st,sn); break; // Tag und Nacht
}
SendMessage(K->hStatus,SB_SETTEXT,4|SBT_OWNERDRAW,0);
return;
}
}
{HMENU m=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
TCHAR s1[32],s2[64],t[32];
GetMenuString(m,0x60+(K->kWetter&15),s2,elemof(s2),0);
DestroyMenu(m);
StripAmpersand(s2);
TagesName((K->kWetter>>4)&3,s1,elemof(s1),0);
LoadString(ghInstance,69,t,elemof(t));
wnsprintf(s,elemof(s),t,s1,s2);
}
}
SendMessage(K->hStatus,SB_SETTEXT,4,(LPARAM)s);
}
/***************
* Treffertest *
***************/
static void SetHoverStaat(karte *K, char value) {
if (K->HoverStaat==value) return;
K->HoverStaat=value;
if (K->kShow&0x40) StatusStaat(K);
}
static int SearchHoverStaat(const karte *K) {
return SearchStaat(K,
cp2bp(K,K->MousePos.x,SB_HORZ),
cp2bp(K,K->MousePos.y,SB_VERT));
}
static void SetHoverIndex(karte *K, char value) {
if (K->HoverIndex==value) return;
if (K->kVis&0x20) InvalHoverIndex(K);
K->HoverIndex=value;
if (K->kVis&0x20) InvalHoverIndex(K);
InvalSymbChange(K,90);
if (K->kShow&0x40) {
StatusRegion(K);
StatusWetter(K); // möglicherweise Schwerwetteranzeige
}
if (K->kWetter&0x80) SetShowRegion(value);
}
// Sucht, welche Vorhersageregion zur angegebenen Kartenpixel-Koordinate passt
static int SearchIndex(const karte *K, int x, int y) {
int i;
for (i=91; --i>=0; ) {
MAPMEM *m=K->map[i];
HRGN rgn=CreatePolygonRgn(m->Poly,m->nPoly,ALTERNATE);
if (PtInRegion(rgn,x,y)) {
DeleteObject(rgn);
return i;
}
DeleteObject(rgn);
}
return -1;
}
// Sucht, welche Vorhersageregion zur (gerade geänderten) Mausposition passt
static int SearchHoverIndex(const karte *K) {
return SearchIndex(K,
cp2bp(K,K->MousePos.x,SB_HORZ),
cp2bp(K,K->MousePos.y,SB_VERT));
}
/**************
* Rollbalken *
**************/
// Kopiert das Verhalten von SetScrollInfo(): verändert nPage und nPos: Nicht ganz!
// Sondern erlaubt nPage > nMax-nMin+1 (weiße Fensterflächen);
// für diesen Fall wird nPos derart begrenzt, dass die Bitmap beliebig im Fenster
// platziert wird, aber nicht herausfällt.
// In diesem Fall begrenzt SetScrollInfo(), und GetScrollPos() darf nicht benutzt werden.
// Scrollbalken verschwinden sowieso, so dass keine SB_THUMBTRACK-Nachricht kommt.
// Voraussetzung: nMin<=nMax!
// Liefert neues si->nPos
static int LimitScroll(SCROLLINFO *si) {
if (si->nMin<=si->nMax) {
int a=si->nMin; // Anfang
int e=si->nMax-si->nPage+1; // Ende (bzw. Anfang für d<si->nPage)
int p=si->nPos;
if (a>e) {
a=e;
e=si->nMin; // tauschen
}
SETMAX(p,a);
SETMIN(p,e);
if (si->nPos!=p) DbgPrintf(("LimitScroll: alt=%d, neu=%d\n",si->nPos,p));
si->nPos=p;
}
return si->nPos;
}
// Schiebe Scrollbalken um oder auf bestimmte Position
// Liefert tatsächliche Verschiebedifferenz, für ScrollWindow()
static int GenShift(karte *K, int nBar, char kode, int dx) {
SCROLLINFO *si=K->ScrollInfo+nBar;
int d=si->nPos;
switch (kode) {
case SB_LINELEFT:
case SB_LINERIGHT: {
dx=8;
if (GetKeyState(VK_SHIFT)<0) dx<<=2;
if (kode==SB_LINELEFT) dx=-dx;
}break;
case SB_PAGELEFT: dx=si->nPage; break;
case SB_PAGERIGHT: dx=-(int)si->nPage; break;
case SB_THUMBTRACK:
case SB_THUMBPOSITION: { // kann nur vorkommen, wenn Rollbalken sichtbar
si->fMask=SIF_TRACKPOS; // Position (als 32-Bit-Wert) holen
GetScrollInfo(K->hMap,nBar,si);
si->nPos=si->nTrackPos; // dx sollte hier 0 sein!
}break;
case SB_LEFT: si->nPos=si->nMin; break;
case SB_RIGHT: si->nPos=si->nMax; break;
}
si->nPos+=dx;
LimitScroll(si);
if (K->kShow&4<<nBar) {
si->fMask=SIF_POS; // reicht
SetScrollInfo(K->hMap,nBar,si,TRUE);
}
if (si->nPos!=d) DbgPrintf(("GenShift(%d,%d,%d): alt=%d, neu=%d\n",nBar,kode,dx,d,si->nPos));
return d-si->nPos;
}
// entweder wenn sich die Mausposition ändert
// oder sich die Karte unter der Maus verschiebt
static void MousePosChanged(karte*K) {
#ifdef POLYEDIT
if (K->kShow&0x40) StatusMercator(K);
#else
SetHoverStaat(K,(char)SearchHoverStaat(K));
#endif
SetHoverIndex(K,(char)SearchHoverIndex(K)); // setzt ggf. Statuszeile
if (K->kShow&0x40) StatusGeo(K);
}
// Fensterinhalt um <dx> und <dy> verschieben
// liefert <true> wenn tatsächlich verschoben wurde
static bool Scroll(karte *K, int dx, int dy) {
ScrollWindow(K->hMap,dx,dy,&K->ScrollRect,NULL);
if (dx||dy) {
InvalSymbChange(K,90);
if (K->hMini) InvalidateRect(K->hMini,NULL,FALSE);
if (!K->bFromMouse) MousePosChanged(K);
return true;
}
return false;
}
static bool HandleHScroll(karte *K, char kode) {
return Scroll(K,GenShift(K,SB_HORZ,kode,0),0);
}
static bool HandleVScroll(karte *K, char kode) {
return Scroll(K,0,GenShift(K,SB_VERT,kode,0));
}
// Schieben mit Tastatur oder Maus
// Liefert <true> wenn tatsächlich geschoben wurde
static bool HandleShift(karte *K, int dx, int dy) {
return Scroll(K,GenShift(K,SB_HORZ,-1,-dx),GenShift(K,SB_VERT,-1,-dy));
}
#pragma optimize("g",off)
// Setzt Scrollbereich, Scrollpage
// Aufzurufen bei WM_SIZE und wenn sich Skalierung ändert
// MousePos.x/y: Client-Position, die bei Größenänderung konstant gehalten soll
// Liefert Verschiebewert
static int AdjustScroll(karte *K, int nBar) {
SCROLLINFO *si=K->ScrollInfo+nBar; // passende Info
int x=si->nPos; // Vorherige Position
unsigned b=si->nMax-si->nMin+1; // Vorhergehende Ausdehnung
si->nMin=scale(K,(&K->MapExtent.left)[nBar]); // SB_VERT -> .top
si->nMax=scale(K,(&K->MapExtent.right)[nBar])-1; // SB_VERT -> .bottom
si->nPage=ScrollRectExt(K,nBar); // Neue Seitengröße
if (b>1) {
int cc; // Zentrierposition
if (K->bFromMouse) cc=(&K->MousePos.x)[nBar]-(&K->ScrollRect.left)[nBar]; // Mausposition
else cc=si->nPage>>1; // Mitte Client
si->nPos=MulDiv(x+cc,si->nMax-si->nMin+1,b)-cc; // Kartenpixel unter der Maus beibehalten
}
LimitScroll(si);
if (K->kShow&0x04<<nBar) {
si->fMask=SIF_ALL;
SetScrollInfo(K->hMap,nBar,si,TRUE);
}
if (x!=si->nPos) DbgPrintf(("AdjustScroll(%d): alt=%d, neu=%d\n",nBar,x,si->nPos));
return x-si->nPos;
}
// Setzt Scrollbereich, Scrollpage, ggf. wird ScrollWindow() aufgerufen
// Aufzurufen bei WM_SIZE und wenn sich Skalierung ändert
static bool AdjustScrollRange(karte *K) {
return Scroll(K,AdjustScroll(K,SB_HORZ),AdjustScroll(K,SB_VERT));
}
#pragma optimize("g",on)
/*****************************************
* Gleitkomma, Koordinatentransformation *
*****************************************/
int FloatToStr(float v, int nk, PTSTR s, int len) {
#if 1 // mit der Kneifzange
TCHAR t[6];
double f=exp(nk*LN10); // Faktor für Nachkommastellen
// FormatMessage() unterstützt variable Feldbreiten mit "*"
// Aber nicht unter Win98!!
// GetNumberFormat() würde sich ebenfalls um Komma sowie Minus kümmern (Test?)
int i=lrint(v*f);
lstrcpy(t,T("%0*d"));
t[2]=nk+(i<0?'2':'1');
i=wnsprintf(s,len-1,t,i);
if (nk) {
int j;
i++; nk++;
j=i;
do s[j]=s[j-1], j--; while(--nk);
s[j]=sDecimal[0];
}
#else // mit msvcrt.dll
int i=_sntprintf(s,len,T("%.*f"),nk,v);
PTSTR p=StrChr(s,'.');
if (p) *p=sDecimal[0];
#endif
return i;
}
// MAPDATA-Zeiger um <index> Einträge vorrücken
static const MAPDATA *IterateMapData(const MAPDATA*rp, BYTE index) {
if (rp && index) do (BYTE*)rp+=sizeof(*rp)+rp->nPoly*sizeof(rp->PolyDiff[0]); while(--index);
return rp;
}
// Polygondaten zu einer Region (0..89) beschaffen
static const MAPDATA *GetMapData(BYTE index) {
static HGLOBAL hMap;
if (index<90) {
if (!hMap) hMap=LoadResource(0,FindResource(0,MAKEINTRESOURCE(48),RT_RCDATA));
if (hMap) return IterateMapData(LockResource(hMap),index);
}
return NULL;
}
int GetCityX(BYTE index) {
if (index==90) return Config.uPos.x;
else{
const MAPDATA *rp=GetMapData(index);
return rp ? rp->CityPos.x : 0;
}
}
int GetCityY(BYTE index) {
if (index==90) return Config.uPos.y;
else{
const MAPDATA *rp=GetMapData(index);
return rp ? rp->CityPos.y : 0;
}
}
PointF GetCityKoord(BYTE index) {
PointF pf={0,0};
if (index==90) {
pf.X=(float)Config.uPos.x;
pf.Y=(float)Config.uPos.y;
}else{
const MAPDATA *rp=GetMapData(index);
if (rp) {
pf.X=(float)rp->CityPos.x;
pf.Y=(float)rp->CityPos.y;
}
}
return UnmapMercator(pf); // jetzt Angabe in Grad
}
/***************************************
* Automatische Fenstergrößenanpassung *
***************************************/
// Struct für folgendes MonitorEnumProc()
typedef struct{
RECT wr,work; // wr.right=Breite, wr.bottom=Höhe, gilt aber nicht für <work>
MONITORINFO mi;
bool repeat; // Horz/Vert Wachstum wurde vorgenommen
}edm_t;
// horizontal (oder vertikal bei verschobenen Zeigern) erweitern, wenn nötig
static bool extend(edm_t*E) {
// Wo grenzt er an?
if (E->wr.right<=E->work.right-E->work.left) return false; // Erweiterung von Interesse?
if (E->work.left==E->mi.rcWork.right) E->work.left=E->mi.rcWork.left; // links dransetzen
else if (E->work.right==E->mi.rcWork.left) E->work.right=E->mi.rcWork.right;// rechts dransetzen
else return false;
return true;
}
// Problem: Der Algorithmus arbeitet nicht für 4 im Quadrat angeordnete Monitore!
static void clip(edm_t*E) {
SETMAX(E->work.left,E->mi.rcWork.left);
SETMIN(E->work.right,E->mi.rcWork.right);
}
static BOOL CALLBACK MonitorEnumProc(HMONITOR mon, HDC dc, RECT*rc, LPARAM data) {
edm_t *E=(edm_t*)data;
GetMonitorInfo(mon,&E->mi);
if (extend(E)) {clip((edm_t*)&E->wr.top); E->repeat=true; return FALSE;}
// Verschobene Zeiger übergeben für die gleiche Prozedur, so arbeitet sie vertikal
if (extend((edm_t*)&E->wr.top)) {clip(E); E->repeat=true; return FALSE;}
return TRUE;
}
// <K> ist nicht const, weil dabei WM_SIZE verarbeitet wird
static void AdjustWindowSize(karte*K) {
if (K->kAutoScale&4 && !IsZoomed(K->hMain) && !IsMinimized(K->hMain)) { // Falls überhaupt
int dx=K->ScrollInfo[SB_HORZ].nMax-K->ScrollInfo[SB_HORZ].nMin+1-ScrollRectExt(K,SB_HORZ); // Wachstums-Wunsch des Fensters
int dy=K->ScrollInfo[SB_VERT].nMax-K->ScrollInfo[SB_VERT].nMin+1-ScrollRectExt(K,SB_VERT);
if (dx||dy) {
POINT fix;
edm_t E;
HMONITOR mon;
if (K->bFromMouse) fix=K->MousePos;
else{
fix.x=(K->ScrollRect.left+K->ScrollRect.right)>>1; // Mitte Client-Bereich
fix.y=(K->ScrollRect.top+K->ScrollRect.bottom)>>1;
}
ClientToScreen(K->hMap,&fix);
GetWindowRect(K->hMain,&E.wr);
DbgPrintf(("AdjustWindowSize: delta=(%d,%d), wr=(%d,%d,%d,%d), fix=(%d,%d)\n",
dx,dy,E.wr.left,E.wr.top,E.wr.right,E.wr.bottom,fix.x,fix.y));
E.wr.right-=E.wr.left; // ab jetzt: Breite
E.wr.bottom-=E.wr.top; // ab jetzt: Höhe
E.wr.left-=MulDiv(fix.x-E.wr.left,dx,E.wr.right);
E.wr.top -=MulDiv(fix.y-E.wr.top,dy,E.wr.bottom);
E.wr.right+=dx;
E.wr.bottom+=dy;
mon=MonitorFromWindow(K->hMain,MONITOR_DEFAULTTONEAREST);
E.mi.cbSize=sizeof(E.mi);
GetMonitorInfo(mon,&E.mi);
CopyRect(&E.work,&E.mi.rcWork);
if (K->kAutoScale&8) { // Arbeitsbereich erweitern (nichttrivial!!)
do{
E.repeat=false;
if (!EnumDisplayMonitors(0,NULL,MonitorEnumProc,(LPARAM)&E)) break;
}while(E.repeat); // wiederholen wenn eine Erweiterung vorgenommen wurde
}
// Eingrenzen auf vollständige Sichtbarkeit
E.work.right-=E.work.left;
E.work.bottom-=E.work.top;
SETMAX(E.wr.left,E.work.left); // Nicht links/oben herausfallen!
SETMAX(E.wr.top,E.work.top); // (sonst kann Titelleiste außerhalb geraten)
SETMIN(E.wr.right,E.work.right); // Ausdehnung begrenzen
SETMIN(E.wr.bottom,E.work.bottom);
dx=E.wr.left+E.wr.right-(E.work.left+E.work.right); // Überhang rechts
dy=E.wr.top+E.wr.bottom-(E.work.top+E.work.bottom); // Überhang unten
if (dx>0) E.wr.left-=dx;
if (dy>0) E.wr.top -=dy;
DbgPrintf(("SetWindowPos: wr=(%d,%d,%d,%d)\n",E.wr.left,E.wr.top,E.wr.right,E.wr.bottom));
SetWindowPos(K->hMain,0,E.wr.left,E.wr.top,E.wr.right,E.wr.bottom,SWP_NOZORDER|SWP_DRAWFRAME);
// Normalerweise sollte sich SetWindowPlacement() um alles kümmern, aber da wimmelt es von Bugs:
// Hilfe-Aussage: kümmert sich im Gegensatz zu SetWindowPos() um Bildschirmgrenzen: nee! (W2k)
// Um die Größenbegrenzung kümmert sich SetWindowPos(), belässt aber etwas zu viel. (W2k,7)
}
}
}
/****************
* Mini-Fenster *
****************/
// <rc> enthält Breite/Höhe statt rechts/unten
// Die linke obere Ecke wird hier „berechnet“
static void MoveMiniRect(const karte *K, RECT *rc) {
char kMini=K->kMini; // lokale Kopie
switch (kMini&3) {
case 1: if (rc->left>-32768) break; // Position je nach Andockung berechnen
case 2: { // außen
RECT wr;
GetWindowRect(K->hMain,&wr);
rc->left = kMini&4 ? wr.right : wr.left-rc->right;
rc->top = kMini&8 ? wr.bottom-rc->bottom : wr.top;
}break;
case 3: { // innen
rc->left = kMini&4 ? K->ScrollRect.right-rc->right : K->ScrollRect.left;
rc->top = kMini&8 ? K->ScrollRect.bottom-rc->bottom : K->ScrollRect.top;
}break;
}
}
// aufzurufen wenn sich hMap ändert - wegen Rollbalken, oder wenn sich ebenso deswegen K->ScrollRect ändert
static void MiniParentChange(const karte *K) {
switch (K->kMini&3) {
case 3: { // innen?
if (GetParent(K->hMini)!=K->hMap) SetParent(K->hMini,K->hMap);
InvalidateRect(K->hMini,NULL,FALSE); // Polygon-Eselsohr kann sich ändern
}nobreak;
case 2: { // außen?
RECT wr;
GetWindowRect(K->hMini,&wr);
wr.right-=wr.left; // Höhe
wr.bottom-=wr.top; // Breite
MoveMiniRect(K,&wr); // hierfür benötigt
SetWindowPos(K->hMini,0,wr.left,wr.top,0,0,SWP_NOSIZE|SWP_NOZORDER);
}break;
}
}
// Client-Position in Mini-Position
static int unscale2mini(const karte *K, int p, int nBar) {
return MulDiv(cp2bp(K,p,nBar)-(&K->MapExtent.left)[nBar],1,K->MiniScale);
}
static void SaveMiniPos(karte *K) {
if ((K->kMini&3)==1) {
RECT wr;
GetWindowRect(K->hMini,&wr);
K->kMiniPos=*(POINT*)≀
if (K->hMain==hFirst) {
Config.kMiniPos.x=(short)wr.left;
Config.kMiniPos.y=(short)wr.top;
}
}
}
/**********************
* Menü-Hilfsroutinen *
**********************/
static void PrepareMenu(karte *K, HMENU m) {
int TagNr;
SetCheckMenuGroup(m,0x10,0x17,K->kShow);
CheckMenuRadioItem(m,0x20,0x23,0x20+(K->kMini&3),0); // „Miniatur“: Darstellungsform
CheckMenuRadioItem(m,0x24,0x27,0x24+(K->kMini>>2&3),0); // Position
EnableMenuGroup(m,0x24,0x27,K->kMini&2?MF_ENABLED:MF_GRAYED); // Positionen ausgrauen
CheckMenuItem(m,0x28,K->kMini&0x10?MF_CHECKED:MF_UNCHECKED);
EnableMenuItem(m,0x28,K->kMini&3?MF_ENABLED:MF_GRAYED);
EnableMenuItem(m,0x2A,K->kMini&3&&(K->kMini>>5<3)?MF_ENABLED:MF_GRAYED);
EnableMenuItem(m,0x2B,K->kMini&3&&(K->kMini>>5>-4)?MF_ENABLED:MF_GRAYED);
EnableMenuItem(m,0x30,K->pPicture?MF_ENABLED:MF_GRAYED); // „Bitmap“ ausgrauen
SetCheckMenuGroup(m,0x30,0x37,K->kVis); // „Karte“: alle 8 Bits
CheckMenuItem(m,0x44,K->Scale.Z==K->Scale.N?MF_CHECKED:MF_UNCHECKED);
EnableMenuItem(m,0x44,K->Scale.Z==K->Scale.N?MF_GRAYED:MF_ENABLED);
SetCheckMenuGroup(m,0x50,0x53,K->kAutoScale); // ZoomBreite/ZoomHöhe/AutoGrow/AutoShrink
EnableMenuItem(m,0x53,GetSystemMetrics(SM_CMONITORS)>1?MF_ENABLED:MF_GRAYED);
CheckMenuRadioItem(m,0x58,0x5B,0x58+(K->kAutoScale>>4&3),0); // Schriftgröße
CheckMenuRadioItem(m,0x5C,0x5F,0x5C+(K->kAutoScale>>6&3),0); // Wettersymbolgröße
CheckMenuRadioItem(m,0x60,0x6F,0x60+(K->kWetter&15),0);
for (TagNr=0; TagNr<4; TagNr++) {
MENUITEMINFO mii;
TCHAR s[32];
HMENU sm=GetSubMenu(m,4);
if (!sm) sm=GetSubMenu(m,1); // wenn Kontextmenü ausgedünnt
TagesName(TagNr,s,elemof(s),sm);
mii.cbSize=sizeof(mii);
mii.fMask=MIIM_STRING;
mii.dwTypeData=s; // passenden Wochentag einsetzen
SetMenuItemInfo(m,0x70+TagNr,FALSE,&mii); // ModifyMenu() lässt Bitmap verschwinden
}
CheckMenuRadioItem(m,0x70,0x73,0x70+(K->kWetter>>4&3),0);
EnableMenuGroup(m,0x70,0x73,K->kWetter&15?MF_ENABLED:MF_GRAYED); // Tage ausgrauen
SetCheckMenuGroup(m,0x74,0x75,K->kWetter>>6); // Gong + automatisches Update
EnableMenuItem(m,0x75,GetPropWetter()?MF_ENABLED:MF_GRAYED);
// GenSliderMenu(m,0x54);
// GenSliderMenu(m,0x55);
}
static int BitmapUsage;
static HBITMAP Bitmaps[12];
static void AddBitmaps(HMENU m, bool force) {
int i;
MENUITEMINFO mii;
if (!BitmapUsage++ || force) {
HDC odc=GetDC(0);
HDC dc=CreateCompatibleDC(odc);
// HDC sdc=CreateCompatibleDC(dc);
// HBITMAP sbm=SelectBitmap(sdc,LoadBitmap(ghThisInst,MAKEINTRESOURCE(2)));
HIMAGELIST il=ImageList_LoadBitmap(ghThisInst,MAKEINTRESOURCE(2),16,0,RGB(192,192,192));
HBITMAP obm=GetCurrentObject(dc,OBJ_BITMAP);
HBRUSH obr=GetSysColorBrush(COLOR_MENU);
int bmwidth=20;
#if 1
BOOL flat=FALSE;
SystemParametersInfo(0x1022/*SPI_GETFLATMENU*/,0,&flat,0);
if (flat) {
bmwidth=16;
#if 0
MENUINFO mi;
mi.cbSize=sizeof(mi);
mi.fMask=MIM_BACKGROUND;
GetMenuInfo(m,&mi);
obr=mi.hbrBack; // Hier kommt Murks (weiß) heraus, wenn ohne Teletubbieoptik
#endif
}
#endif
obr=SelectBrush(dc,obr);
for (i=0; i<elemof(Bitmaps); i++) {
SelectBitmap(dc,Bitmaps[i]=CreateCompatibleBitmap(odc,bmwidth,15));
PatBlt(dc,0,0,bmwidth,15,PATCOPY);
// TransparentBlt(dc,0,0,16,15,sdc,i<<4,0,16,15,RGB(192,192,192));
ImageList_Draw(il,i,dc,0,0,0);
}
ReleaseDC(0,odc);
SelectBitmap(dc,obm);
SelectBrush(dc,obr);
// DeleteBitmap(SelectBitmap(sdc,sbm));
ImageList_Destroy(il);
DeleteDC(dc);
// DeleteDC(sdc);
}
mii.cbSize=sizeof(mii);
mii.fMask=MIIM_BITMAP;
for(i=0; i<elemof(Bitmaps); i++) {
mii.hbmpItem=Bitmaps[i];
SetMenuItemInfo(m,0x60+(i&8)+i,FALSE,&mii);
}
}
static void DelBitmaps(bool force) {
if (!--BitmapUsage || force) {
int i;
for (i=0; i<elemof(Bitmaps); i++) DeleteBitmap(Bitmaps[i]);
}
if (BitmapUsage<0) DbgPrintf(("DelBitmaps: BUG: BitmapUsage negativ! (%d)\n",BitmapUsage));
}
/*********************
* GUI-Hilfsroutinen *
*********************/
static void InitToolInfo(const karte*K, TOOLINFO *ti) {
InitStruct(ti,sizeof(*ti));
ti->uFlags=TTF_TRACK;
ti->hwnd=K->hMain;
ti->uId=1;
ti->hinst=ghThisInst;
ti->lpszText=LPSTR_TEXTCALLBACK;
}
static void TrackActivate(const karte*K, BOOL fAct) {
TOOLINFO ti;
InitToolInfo(K,&ti);
SendMessage(K->hTip,TTM_TRACKACTIVATE,fAct,(LPARAM)&ti);
}
/**************************
* Variable ändern: kShow *
**************************/
static void AddToolbar(karte*K) {
TBBUTTON buttons[14],*b=buttons;
int i;
__stosd((DWORD*)buttons,0,sizeof(buttons)/4);
for (i=0; i<12; i++,b++) {
if (i==0 || i==8) b++->fsStyle=TBSTYLE_SEP;
b->iBitmap=i;
b->idCommand=0x60+(i&8)+i;
b->fsState=TBSTATE_ENABLED;
b->fsStyle=BTNS_CHECKGROUP;
b->iString=(INT_PTR)-1;
}
K->hToolbar=CreateToolbarEx(K->hMain,WS_VISIBLE|WS_CHILD|TBSTYLE_TOOLTIPS,10,NUMSTAAT,ghThisInst,2,
buttons,elemof(buttons),16,16,16,15,sizeof(TBBUTTON));
}
static void SetToolbarButtons(const karte*K) {
SendMessage(K->hToolbar,TB_CHECKBUTTON,0x60+(K->kWetter&7),TRUE);
SendMessage(K->hToolbar,TB_CHECKBUTTON,0x70+(K->kWetter>>4&3),TRUE);
}
static void kShowChange(karte *K, BYTE xor) {
BYTE o=K->kShow;
BYTE n=K->kShow=o^xor;
bool o2,n2; // 2 Fenster (hMap!=hMain) alt + neu
if (!xor) xor=o, o=0; // Sonderfall Initialisierung
o2 = o&0x60 && o&0x0C;
n2 = n&0x60 && n&0x0C;
if (xor&0x04) ShowScrollBar(K->hMap,SB_HORZ,n&0x04);
if (xor&0x08) ShowScrollBar(K->hMap,SB_VERT,n&0x08);
if (n2 && !o2) ShowScrollBar(K->hMain,SB_BOTH,FALSE);
// if (!SetWindowTheme(K->hMain,NULL,NULL)) UpdateWindow(K->hMain); // angeblich Vista/7-Bugfix
if (xor&0x10) {
if (n&0x10) {
HMENU m=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
DeleteMenu(m,5,MF_BYPOSITION); // Niemals "Meine Position" im Hauptmenü
AddBitmaps(GetSubMenu(m,4),false);
if (!HelpName[0]) DeleteMenu(m,5,MF_BYPOSITION); // Keine "Hilfe" ohne Hilfedatei
SetMenu(K->hMain,m);
}else{
DestroyMenu(GetMenu(K->hMain));
DelBitmaps(false);
SetMenu(K->hMain,0);
}
}
if (xor&0x20) {
if (n&0x20) {
AddToolbar(K);
SetToolbarButtons(K);
}else{
DestroyWindow(K->hToolbar);
K->hToolbar=0; // sollte unnötig sein, wenn man auf kShow testet
}
}
if (xor&0x40) {
if (n&0x40) {
static const int StatusDiv[]={40,170,300,550,-1};
K->hStatus=CreateStatusWindow(WS_VISIBLE|WS_CHILD|SBARS_SIZEGRIP,NULL,K->hMain,11);
SendMessage(K->hStatus,SB_SETPARTS,elemof(StatusDiv),(LPARAM)StatusDiv);
StatusScale(K);
StatusGeo(K);
StatusStaat(K);
StatusRegion(K);
StatusWetter(K);
}else{
DestroyWindow(K->hStatus);
K->hStatus=0;
}
}
if (xor&0x60) {
PostMessage(K->hMain,WM_SIZE,SIZE_RESTORED,0);
InvalidateRect(K->hMain,NULL,TRUE);
}
if (xor&0x80) {
if (n&0x80) {
TOOLINFO ti;
K->hTip=CreateWindowEx(WS_EX_TOPMOST,TOOLTIPS_CLASS,NULL,
WS_POPUP|TTS_NOPREFIX|TTS_ALWAYSTIP,
0,0,0,0,K->hMain,NULL,ghThisInst,NULL);
SendMessage(K->hTip,TTM_SETMAXTIPWIDTH,0,1000);
InitToolInfo(K,&ti);
SendMessage(K->hTip,TTM_ADDTOOL,0,(LPARAM)&ti); // erscheint bei Mausbewegung
}else{
DestroyWindow(K->hTip);
K->hTip=0;
}
}
// Anwesenheitswechsel des Kindfensters detektieren
if (n2^o2) {
if (n2) { // Neues Kindfenster?
RECT rc;
LoadClientRect(K->hMain,&rc);
K->hMap=CreateWindow(KARTECLASSNAME,NULL,WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN,
rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,
K->hMain,(HMENU)12,ghThisInst,K);
}else{
if (K->hMini && GetParent(K->hMini)==K->hMap) SetParent(K->hMini,K->hMain);
DestroyWindow(K->hMap);
K->hMap=K->hMain;
}
SetFocus(K->hMap);
}
if (!n2 && o2) {
if (K->kShow&4) SetScrollInfo(K->hMain,SB_HORZ,K->ScrollInfo+SB_HORZ,TRUE);
if (K->kShow&8) SetScrollInfo(K->hMain,SB_VERT,K->ScrollInfo+SB_VERT,TRUE);
}
LoadClientRect(K->hMap,&K->ScrollRect);
if (n2^o2) MiniParentChange(K);
if (K->hMain==hFirst) Config.kShow=K->kShow;
}
// Liefert Soll-Größe für Client-Bereich
static SIZE MiniExtent(const karte *K) {
SIZE ext={MapExtentExt(K,SB_HORZ)/K->MiniScale,MapExtentExt(K,SB_VERT)/K->MiniScale};
return ext;
}
/**************************
* Variable ändern: kMini *
**************************/
static void CalcMiniScale(karte*K) {
int x,y,b;
x=MulDiv(MapExtentExt(K,SB_HORZ),8,GetSystemMetrics(SM_CXFULLSCREEN));
y=MulDiv(MapExtentExt(K,SB_VERT),8,GetSystemMetrics(SM_CYFULLSCREEN));
SETMAX(x,y);
_BitScanReverse(&b,x); // svw. »logarithmus dualis«
b=(b<<1)+(x>>(b-1)&1); // Nächstes Bit mitnehmen
b+=K->kMini>>5; // Benutzer-Skalierung dazu
SETMAX(b,0);
x=(2|b&1)<<(b>>1); // Exponentialfunktion mit je 1 linearer Zwischenstufe
DbgPrintf(("CalcMiniScale: %d\n",x));
K->MiniScale=(char)x;
}
static void ShowMini(const karte*K) {
if (K->hMini) {
ShowWindow(K->hMini,!(K->kMini&0x10)
|| (int)K->ScrollInfo[SB_HORZ].nPage<=K->ScrollInfo[SB_HORZ].nMax-K->ScrollInfo[SB_HORZ].nMin
|| (int)K->ScrollInfo[SB_VERT].nPage<=K->ScrollInfo[SB_VERT].nMax-K->ScrollInfo[SB_VERT].nMin
?SW_SHOWNA:SW_HIDE);
}
}
static void kMiniSet(karte *K, char n, char mask) {
char o=K->kMini;
char xor;
n=o&~mask|n&mask;
xor=n^o;
if (!mask) xor=o, o=0; // Sonderfall Initialisierung
if (xor&0xE3 && o&3) { // alter Zustand
DestroyWindow(K->hMini); // kann K->kMini verändern!
if (xor&0xE0 && K->MiniBmp && DeleteBitmap(K->MiniBmp)) K->MiniBmp=0;
}
K->kMini=n;
if (xor&0xE0) CalcMiniScale(K);
if (xor&0x10) ShowMini(K);
if (K->hMain==hFirst) {
Config.kMini=n;
}
if (xor&0xE3 && n&3) {
static const WORD styles[3]={
(WS_BORDER|WS_CAPTION|WS_SYSMENU)>>16,
(WS_POPUP|WS_BORDER|WS_THICKFRAME)>>16, // müssen WM_SIZE und WM_NCHITTEST abfangen
(WS_CHILD|WS_BORDER|WS_VISIBLE|WS_CLIPSIBLINGS)>>16}; // ClipSiblings: Statuszeile
DWORD style=styles[(n&3)-1]<<16;
const DWORD exstyle=WS_EX_TOOLWINDOW|WS_EX_WINDOWEDGE;
RECT rc;
SIZE sz=MiniExtent(K);
HMENU m;
TCHAR s[32];
SetRect(&rc,0,0,sz.cx,sz.cy);
AdjustWindowRectEx(&rc,style,FALSE,exstyle);
rc.right-=rc.left; // Breite wiederherstellen
rc.bottom-=rc.top;
*(POINT*)&rc=K->kMiniPos; // enthält zunächst -32768 (Win16 CW_USEDEFAULT)
MoveMiniRect(K,&rc);
m=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
GetMenuString(m,1,s,elemof(s),MF_BYPOSITION);
StripAmpersand(s);
K->hMini=CreateWindowEx(exstyle,KARTECLASSNAME,s,style,
rc.left,rc.top,rc.right,rc.bottom,
~n&3?K->hMain:K->hMap,0,ghThisInst,K);
switch (n&3) {
case 1: {
AnimateWindow(K->hMini,200,AW_BLEND);
}break;
case 2: {
AnimateWindow(K->hMini,200,n&4?AW_SLIDE|AW_HOR_POSITIVE:AW_SLIDE|AW_HOR_NEGATIVE);
// ShowWindow(K->hMini,SW_SHOWNA); // PROBLEM: Beim Starten der Karte wird dieses Fenster dennoch aktiv!
}break;
}
}else if (xor&0x0C) MiniParentChange(K); // (SW_SHOWNOACTIVE tut's nicht.)
}
/**********************************************
* Variable ändern: kVis, kAutoScale, kWetter *
**********************************************/
static void kVisChange(karte *K, BYTE xor) {
K->kVis^=xor;
if (K->hMain==hFirst) Config.kVis=K->kVis;
InvalidateRect(K->hMap,&K->ScrollRect,TRUE);
InvalSymbChange(K,90);
}
// n = neue Bits, mask = maskiert die neuen Bits, xor = dreht Bits
static void kAutoScaleSet(karte*K,BYTE n, BYTE mask, BYTE xor) {
n=(K->kAutoScale&~mask|n&mask)^xor; // sich ergebender neuer Wert
xor=n^K->kAutoScale; // sich ändernde Bits
if (!xor) return;
K->kAutoScale=n;
if (K->hMain==hFirst) Config.kAutoScale=n;
if (n&xor&0x0C) AdjustWindowSize(K); // wenn eins der Bits /gesetzt/ wird
if (xor&0xF0) InvalSymbChange(K,-1);
}
// n = neue Bits, mask = maskiert die neuen Bits, xor = dreht Bits
static void kWetterSet(karte *K, BYTE n, BYTE mask, BYTE xor) {
n=(K->kWetter&~mask|n&mask)^xor; // sich ergebender neuer Wert
xor=n^K->kWetter; // sich ändernde Bits
if (xor) {
K->kWetter=n;
if (K->hMain==hFirst) Config.kWetter=n;
if (xor&0x3F) {
InvalSymbChange(K,-1);
if (K->kShow&0x40) StatusWetter(K);
}
}
if (!mask && !xor) {
xor=0xFF; // Initialisierungsfall
InvalSymbChange(K,-1);
}
if (K->hToolbar && xor&0x3F) SetToolbarButtons(K);
}
static void SetScale(karte *K, char kode) {
char kScale=K->kScale;
char kAutoScale=K->kAutoScale;
if (kode>=0 && kode!=66) {
char lineadd=GetKeyState(VK_SHIFT)<0?1:4; // Kleine Schritte mit gedrückter Shift-Taste
kAutoScale=0;
switch (kode) {
case SB_LINELEFT: kScale-=lineadd; break;
case SB_LINERIGHT: kScale+=lineadd; break;
case SB_PAGELEFT: kScale-=16; break;
case SB_PAGERIGHT: kScale+=16; break;
case 100: kScale=0; break; // 1:1
}
SETMIN(kScale,64); // begrenzen auf 2^4 = 16fach
SETMAX(kScale,-64); // begrenzen auf 2^-4 = 1/16
K->Scale.N=1<<((128-kScale)>>4); // ein rationales Verhältnis draus machen
K->Scale.Z=lrint(exp(kScale/(LOG2E*16))*K->Scale.N);
}else{
int nBar=SB_HORZ; // 0
if (kode==66) goto beide; // in Fenster einpassen, aber nicht kAutoScale setzen
kAutoScale=-kode; // -1, -2 oder -3
switch (kAutoScale&3) {
case 3: beide: // dort, wo's passt!
if ((double)MapExtentExt(K,SB_VERT)/ScrollRectExt(K,SB_VERT)
< (double)MapExtentExt(K,SB_HORZ)/ScrollRectExt(K,SB_HORZ)) break;
case 2: nBar=SB_VERT;
}
K->Scale.Z=ScrollRectExt(K,nBar);
K->Scale.N=MapExtentExt(K,nBar);
kScale=lrint(log((double)K->Scale.Z/K->Scale.N)*(LOG2E*16)); // kScale informativ mitführen
}
K->kScale=kScale;
if (K->hMain==hFirst) {
Config.kScale=kScale;
}
kAutoScaleSet(K,kAutoScale,3,0);
kVisChange(K,0);
AdjustScrollRange(K);
ShowMini(K);
AdjustWindowSize(K);
CalcRects(K);
InvalSymbChange(K,-1);
if (K->kShow&0x40) StatusScale(K);
}
/******************
* TAB-Behandlung *
******************/
// Low-Nibble = Vorhersagetyp (1..7)
// High-Nibble = Tages-Nummer (0..3)
static int CountSymbols(BYTE n) {
int k,r=0;
for (k=0; k<90; k++) {
U64 v=GetTagWetter(n,k);
if (v.ull) {
switch (n&15) {
case 1:
case 3: r++; break; // Tag oder Nacht
case 2:
case 5: if (v.Tag) r++; break; // nur Tag
case 4:
case 6: if (v.Nacht) r++; break; // nur Nacht
case 7: if (!(v.Tag&0x8000) && v.Tag&0x0F00) r++; break;
}
}
}
return r;
}
// liefert TRUE wenn mindestens ein Wettersymbol angezeigt werden kann,
// für Leertaste, Menü(?) und Buttonleiste
// Low-Nibble = Vorhersagetyp
// High-Nibble = Tages-Nummer
static bool AnySymbol(BYTE n) {
n&=0x3F;
if ((BYTE)((n&0x0F)-1)>=6) return false;
switch (n) {
case 0x34:
case 0x36: return false;
}
return CountSymbols(n);
}
// Zum Durchschalten mit Buchstabentaste (nur Anfangsbuchstabe)
// (nicht wie im Explorer durch Eintippen der Anfangsbuchstaben in einem Zeitfenster)
static bool MatchStadt(int k, TCHAR match) {
if (!match) return true;
{TCHAR s[64], *sp=s;
int rl=LoadString(ghInstance,256+k,s,elemof(s));
int sl=lstrlen(s);
if (sl<rl) sp+=sl+1; // tatsächlich zweiteilig? Zeiger auf die Stadt setzen
return !StrCmpNI(sp,&match,1); // Anfangsbuchstabe der angezeigten Stadt
}
}
// Ziehe Rechteck in Mitte; dabei rc in unverschobenen Client-Koordinaten und pt (Schwerpunkt) in Bitmap-Koordinaten
// Für TAB = Durchschalten der Regionen sowie (TODO) Länder-Auswahl
static int MoveRectInto(const karte *K, const RECT *rc, const POINT *pt, int nBar) {
int t;
int l=(&rc->left)[nBar];
int r=(&rc->right)[nBar];
int L=(&K->ScrollRect.left)[nBar];
int R=(&K->ScrollRect.right)[nBar];
if (r-l > R-L) { // passt nicht hinein
int M=(L+R)>>1;
int n=bp2cp(K,(&pt->x)[nBar],nBar);
return M-n; // kann 0 sein
}
t=shift(K,nBar);
l+=t;
r+=t;
if (l<L) return L-l; // nach rechts
if (r>R) return R-r; // nach links
return 0;
}
// Macht das Polygon-Umfangsrechteck sichtbar, indem es in das Fenster gezogen wird.
// Passt es nicht hinein, wird am Schwerpunkt zentriert.
// Wenn's vollständig sichtbar ist, tut diese Funktion nichts.
// Hier: Berechnung der notwendigen Scrolldistanz in Pixel
static int MoveInto(const karte *K, int k, int nBar) {
const MAPMEM *m=K->map[k];
return MoveRectInto(K,&m->rcPoly,&m->Centroid,nBar);
}
static void FocusRegion(karte*K, int k) {
HandleShift(K,MoveInto(K,k,SB_HORZ),MoveInto(K,k,SB_VERT));
SetHoverIndex(K,(char)k); // es funktioniert nicht "smooth", wenn man die Reihenfolge vertauscht!!
}
// Die Tab-Taste setzt die Hover-Funktion der Maus temporär außer Kraft,
// und die numerisch nächste/vorhergehende Region wird fokussiert
// Großbuchstabe (Shift gedrückt) = rückwärts, Kleinbuchstabe (Bit 5 gesetzt) vorwärts
static void HandleTab(karte *K, TCHAR suche) {
char k=K->HoverIndex;
char start=k;
char e=Config.uRegion>=0?91:90;
char dir=GetKeyState(VK_SHIFT)<0?-1:1;
if (start<0) start=dir>=0?e-1:0;
for(;;){
k+=dir;
if (k<0) k=e-1;
if (k>=e) k=0; // umlaufend
if (MatchStadt(k,suche)) break; // Nicht piepsen wenn genau eine Stadt mit dem Buchstaben zugeordnet ist
if (k==start) {MessageBeep(MB_ICONSTOP); return;}
}
FocusRegion(K,k);
}
// "Meine Position" von Karte (und nur dort) löschen
static void deleteMyPos(karte *K) {
MAPMEM *m=K->map[90];
if (m->nPoly) {
InvalIndex(K,90); // alte Region invalidieren (Kreuz wegnehmen)
InvalRect(K,&K->map[90]->rcText); // den Text auch!
}
m->nPoly=0;
}
/*************************************
* Fensterprozedur (Mehrfachnutzung) *
*************************************/
static LRESULT WINAPI MainWndProc(HWND Wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
int i;
karte *K=(karte*)GetWindowLongPtr(Wnd,0);
if (msg==WM_NCCREATE) {
CREATESTRUCT *cs=(CREATESTRUCT*)lParam;
if (cs->lpCreateParams) { // Kartenkind, Miniatur
K=cs->lpCreateParams;
if (cs->style&WS_CHILD && !(cs->style&WS_BORDER)) K->hMap=Wnd; // Kartenkind
else K->hMini=Wnd; // Miniatur
}else{ // allererste Amtshandlung, sonst kracht's
K=LocalAlloc(LPTR,sizeof(karte)-sizeof(BORDEREX)+CalcBorderexSize());
if (K) {
K->hMap=K->hMain=Wnd;
K->kShow=Config.kShow; // Jedes der möglichen Karten-Fenster erbt die gleichen Voreinstellungen.
K->kMini=Config.kMini; // Diese können getrennt verändert werden.
K->kVis=Config.kVis; // Aber nur das primäre Karten-Fenster (hFirst) speichert die Einstellungen.
K->kScale=Config.kScale;
K->kAutoScale=Config.kAutoScale;
K->kWetter=Config.kWetter;
K->ScrollInfo[SB_VERT].cbSize=K->ScrollInfo[SB_HORZ].cbSize=sizeof(SCROLLINFO);
K->HoverIndex=-1;
K->HoverStaat=-1;
K->MyPosStaat=-1;
K->kMiniPos.x=Config.kMiniPos.x; // Freies Mini-Fenster irgendwohin platzieren (wirkt nicht, deshalb Sonderbehandlung!)
K->kMiniPos.y=Config.kMiniPos.y;
}
}
SetWindowLongPtr(Wnd,0,(LONG_PTR)K);
}
if (!K) return DefWindowProc(Wnd,msg,wParam,lParam);
/*** (0) Nachrichtenbehandlung für alle Fenster ***/
K->bFromMouse=false;
if (WM_MOUSEFIRST<=msg && msg<=WM_MOUSELAST) {
K->bFromMouse=true;
if (LOWORD(wParam)) SetCapture(Wnd); else ReleaseCapture();
}
/*** (A) Kartenkind ***/
if (Wnd==K->hMap && Wnd!=K->hMain) switch (msg) {
case WM_NCDESTROY: K->hMap=K->hMain; break;
/*** (B) Miniatur ***/
}else if (Wnd==K->hMini) switch (msg) {
case WM_CREATE: {
if (!K->MiniBmp) {
SIZE sz=MiniExtent(K);
HDC dc=GetDC(Wnd);
HDC memdc=CreateCompatibleDC(dc);
HBITMAP obm;
K->MiniBmp=CreateCompatibleBitmap(dc,sz.cx,sz.cy);
ReleaseDC(Wnd,dc);
obm=SelectBitmap(memdc,K->MiniBmp);
PatBlt(memdc,0,0,sz.cx,sz.cy,PATCOPY);
SetMapMode(memdc,MM_ANISOTROPIC);
SetWindowExtEx(memdc,K->MiniScale,K->MiniScale,NULL);
SetWindowOrgEx(memdc,K->MapExtent.left,K->MapExtent.top,NULL);
PaintPicture(K,memdc);
SelectBitmap(memdc,obm);
DeleteDC(memdc);
}
}break;
case WM_CLOSE: { // Klick aufs rote X
SaveMiniPos(K);
K->kMini&=~3;
if (K->hMain==hFirst) Config.kMini=K->kMini;
}break;
case WM_DESTROY: SaveMiniPos(K); break;
case WM_NCDESTROY: K->hMini=0; break; // Referenz vernichten
case WM_LBUTTONDOWN: {
K->MousePos.x=GET_X_LPARAM(lParam);
K->MousePos.y=GET_Y_LPARAM(lParam);
}return 0;
case WM_MOUSEMOVE: if (wParam&MK_LBUTTON) {
POINT n={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
POINT d; // Delta
d.x=n.x-K->MousePos.x;
d.y=n.y-K->MousePos.y;
K->MousePos=n;
if (HandleShift(K,scale(K,d.x*-K->MiniScale),scale(K,d.y*-K->MiniScale)))
UpdateWindow(Wnd); // vorrangig behandeln: WM_PAINT senden für direktes Feedback
}return 0;
case WM_CONTEXTMENU: {
HMENU m=CreateMenu();
HMENU sm=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
int i;
for (i=0;i<6; i++) DeleteMenu(sm,i?1:0,MF_BYPOSITION); // Nur "Miniatur" stehen lassen
PrepareMenu(K,sm);
InsertMenu(m,0,MF_BYPOSITION|MF_POPUP,(UINT)sm,NULL); // sonst geht's nicht!
TrackPopupMenu(sm,TPM_RIGHTBUTTON,
GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),0,K->hMain,NULL);
DestroyMenu(m);
}return 0;
case WM_SIZE: { // Client-Größe prüfen und Fenster ggf. ausdehnen
SIZE ist={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
SIZE soll=MiniExtent(K);
if (*(__int64*)&soll!=*(__int64*)&ist) { // Veränderung?
RECT wr;
GetWindowRect(Wnd,&wr);
wr.right+=soll.cx-ist.cx-wr.left; // neue Größe
wr.bottom+=soll.cy-ist.cy-wr.top;
MoveMiniRect(K,&wr);
SetWindowPos(Wnd,0,wr.left,wr.top,wr.right,wr.bottom,SWP_NOZORDER);
}
}return 0;
case WM_PRINTCLIENT: {
RECT rc;
HDC dc=(HDC)wParam;
SetGraphicsMode(dc,GM_ADVANCED);
BltBitmap(dc,K->MiniBmp);
SetRect(&rc, // Sichtbaren Ausschnitt umrechnen
unscale2mini(K,K->ScrollRect.left,SB_HORZ),
unscale2mini(K,K->ScrollRect.top,SB_VERT),
unscale2mini(K,K->ScrollRect.right,SB_HORZ)-2,
unscale2mini(K,K->ScrollRect.bottom,SB_VERT)-2);
SelectPen(dc,gdi.p[4]);
SelectBrush(dc,GetStockObject(HOLLOW_BRUSH));
if ((K->kMini&3)==3) { // bei Innen-Andockung „Eselsohr“ anbasteln
static const DWORD PolyGen[4]={ //0=rc.left,1=rc.right,2=wr.left,3=wr.right
0x8A23FC, // 0333 3010 1101
0x8AED10, // 0020 2313 1101
0x47BA20, // 0010 1131 3202
0x895620}; // 0010 1222 2101
RECT wr;
POINT poly[6];
DWORD pg=PolyGen[(K->kMini>>2)&3];
GetWindowRect(Wnd,&wr);
MapWindowPoints(0,K->hMap,(POINT*)&wr,2);
SetRect(&wr,
unscale2mini(K,wr.left,SB_HORZ),
unscale2mini(K,wr.top,SB_VERT),
unscale2mini(K,wr.right,SB_HORZ)-2,
unscale2mini(K,wr.bottom,SB_VERT)-2);
for (i=0; i<6; i++) {
poly[i].x = (pg&1?&wr.left:&rc.left)[pg&2?2:0];
poly[i].y = (pg&4?&wr.top:&rc.top)[pg&8?2:0];
pg>>=4;
}
Polygon(dc,poly,6);
}else{
Rectangle(dc,rc.left,rc.top,rc.right,rc.bottom);
}
}return 0;
case WM_PAINT: {
PAINTSTRUCT ps;
BeginPaint(Wnd,&ps);
SendMessage(Wnd,WM_PRINTCLIENT,(WPARAM)ps.hdc,PRF_CLIENT);
EndPaint(Wnd,&ps);
}return 0;
case WM_NCHITTEST: {
LRESULT r=DefWindowProc(Wnd,msg,wParam,lParam);
switch (K->kMini&3) {
case 1: if (r==HTBORDER) r=HTCAPTION; break; // Rand zum Verschieben benutzen
case 2: if (r!=HTCLIENT) r=HTNOWHERE; break; // Nichts ist verschieblich
}
return r;
}
case WM_MOUSEACTIVATE: {
if ((K->kMini&3)==2) return MA_NOACTIVATE;
}break;
/*** (C) Nachrichtenbehandlung als Hauptfenster ***/
}else switch (msg) {
case WM_CREATE: {
const MAPDATA *rp;
const BORDER *bo;
TCHAR buf[MAX_PATH];
int k,i;
if (!hFirst) {
WINDOWPLACEMENT wp;
hFirst=Wnd;
wp.length=sizeof(wp);
GetWindowPlacement(Wnd,&wp);
if (Config.kShow&3) wp.showCmd=Config.kShow&3;
if (Config.kPos.right-Config.kPos.left>=64
&& Config.kPos.bottom-Config.kPos.top>=64) {
wp.rcNormalPosition.left =Config.kPos.left; // Kümmert sich Windows hier um die Darstellbarkeit??
wp.rcNormalPosition.top =Config.kPos.top; // Angeblich ja ...
wp.rcNormalPosition.right =Config.kPos.right;
wp.rcNormalPosition.bottom=Config.kPos.bottom;
}
SetWindowPlacement(Wnd,&wp);
}
K->kInfo=&KINFO_NONE; // hier kein Null-Zeiger
if (GetKeyState(VK_CONTROL)>=0) {
LocateFile(buf,KINFO_EUROPA.name);
if (buf[0] && (K->pPicture=LoadPictureFile(buf))) {
K->kInfo=&KINFO_EUROPA;
goto done;
}
}
LocateFile(buf,KINFO_EUROPE.name);
if (buf[0] && (K->hPicture=pngload(buf))) {
K->kInfo=&KINFO_EUROPE;
K->bPictureHBM=true;
goto done;
}
LocateFile(buf,KINFO_ECLIPSE.name);
if (buf[0] && (K->pPicture=LoadPictureFile(buf))) {
K->kInfo=&KINFO_ECLIPSE;
}
done:
SetRect(&K->MapExtent,0,0,K->kInfo->szBitmap.cx,K->kInfo->szBitmap.cy);
rp=GetMapData(0);
for (k=0; k<90; k++) {
POINT start;
K->map[k]=(MAPMEM*)LocalAlloc(LPTR,sizeof(MAPMEM)+rp->nPoly*sizeof(POINT));
K->map[k]->nPoly=rp->nPoly;
K->map[k]->CitySize=rp->CitySize&0x0F;
//K->map[k]->TextPos=4;//rp->CitySize>>4;
start.x=rp->CityPos.x;
start.y=rp->CityPos.y;
K->map[k]->CityPos=MapCoord(K,&start);
for (i=0; i<rp->nPoly; i++) {
POINT p;
start.x+=rp->PolyDiff[i].x;
start.y+=rp->PolyDiff[i].y;
K->map[k]->Poly[i]=p=MapCoord(K,&start);
InflateRectForPoint(&K->MapExtent,&p);
}
rp=IterateMapData(rp,1);
}
bo=GetBorder();
if (bo) {
BORDEREX *boe=&K->bo;
for (; bo->n; bo=IterateBorder(bo), boe=IterateBorderex(boe)) {
int i;
POINT p={bo->start.x,bo->start.y},*pt=boe->points;
const POINTC *ptc=bo->Diff;
boe->hdr=bo->hdr; // Header 1:1 kopieren
for(i=bo->n;;pt++,ptc++) {
*pt=MapCoord(K,&p); // Punkt umrechnen (langsam!)
if (!--i) break;
p.x+=ptc->x; // Nächste Differenz nehmen
p.y+=ptc->y;
}
}
boe->valid=0; // eigentlich unnötig, da Speicher ausgenullt
CreateStaatenRegions(K);
}
K->map[90]=(MAPMEM*)LocalAlloc(LPTR,sizeof(MAPMEM)+12*sizeof(POINT));
if (Config.uRegion>=0) CalcMyPos(K,true);
K->flags=ImageList_LoadBitmap(ghThisInst,MAKEINTRESOURCE(1),16,0,RGB(255,0,255));
CalcMiniScale(K);
CalcRects(K);
CreateGdiHandles();
K->bInitialized=true;
CalcMondalter(); // falls noch kein Wetterdialog da ist
SetScale(K,42);
HandleShift(K,-Config.kScrl.x,-Config.kScrl.y);
kShowChange(K,0); // löst WM_SIZE aus
if (Wnd==hFirst) {
if (Config.Checkmarks&0x80) PropRespawn();
else PropAppendSysMenu();
}
kMiniSet(K,0,0);
kWetterSet(K,0,0,0);
}break;
case WM_ACTIVATE: if (wParam) SetFocus(K->hMap); return 0; // sonst kommen keine fokussierten Ereignisse an
case WM_SIZE: {
if (K->kShow&0x20) SendMessage(K->hToolbar,msg,wParam,lParam);
if (K->kShow&0x40) SendMessage(K->hStatus,msg,wParam,lParam);
if (K->hMap!=Wnd) {
RECT rc;
LoadClientRect(Wnd,&rc);
MoveWindow(K->hMap,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,TRUE);
}
}nobreak;
case WM_MOVE: {
if ((K->kMini&3)==2) MiniParentChange(K);
}break;
case WM_INITMENU: {
PrepareMenu(K,(HMENU)wParam);
}break;
case WM_FUNKRECV: switch ((BYTE)wParam) {
case 16: {
DWORD d=IndexToRegion((DWORD)lParam);
if (((K->kWetter>>4)&3)!=HIBYTE(HIWORD(d))) return 0; // falscher Tag
switch (K->kWetter&15) {
case 1:
case 3:
case 7: break; // Tag & Nacht piepsen
case 2:
case 5: if (LOBYTE(LOWORD(d))) return 0; break; // Nachtinfo? Nicht piepsen!
case 4:
case 6: if (LOBYTE(LOWORD(d))) break; return 0; // Taginfo? Nicht piepsen!
default: return 0; // alles andere: nicht piepsen
}
InvalSymbChange(K,LOBYTE(HIWORD(d)));
// Jetzt könnte man noch gucken, ob das Symbol im Kartenausschnitt ist ...
// Und wie könnte man das neue Item animieren?
}goto flash;
case 17: {
InvalSymbChange(K,-1); // alle Wettersymbole ändern sich!
flash:
if (K->kWetter&0x40) {
FLASHWINFO fwi={sizeof(fwi),Wnd,FLASHW_ALL,3};
FlashWindowEx(&fwi); // Bringt unter W2k keine optische Verbesseung!
// FlashWindow(Wnd,TRUE);
SetTimer(Wnd,0x40,5000,NULL);
if (Wnd==hFirst) {
MessageBeep(MB_ICONINFORMATION);
}
}
}break; // neu zeichnen (fällt das auf?)
case 18: if ((char)lParam!=Config.Region) { // Config.Region geändert: Bild aktualisieren, lParam = alter Wert
InvalIndex(K,(int)lParam);
InvalIndex(K,Config.Region);
InvalSymbChange(K,90);
}break;
case 19: if (lParam!=1) { // Config.uPos oder Vorzeichen von Config.uRegion geändert
deleteMyPos(K);
if (Config.uRegion>=0) CalcMyPos(K,true), CalcRects1(K,K->map[90]);
InvalIndex(K,90);
InvalSymbChange(K,90);
}break;
}break;
case WM_MyPosStaat: return K->MyPosStaat;
case WM_FocusRegion: FocusRegion(K,(int)wParam); break;
// case WM_TIMER: KillTimer(Wnd,wParam); switch (wParam) {
// case 0x40: FlashWindow(Wnd,FALSE); break;
// }break;
case WM_DRAWITEM: if (wParam==11) { // hier: Statuszeile
DRAWITEMSTRUCT*dis=(DRAWITEMSTRUCT*)lParam;
int h=dis->rcItem.bottom-dis->rcItem.top; // h = 16 (W2k), = 19 (XP; W2k Chemnitz)
int m=(dis->rcItem.bottom+dis->rcItem.top-1)>>1; // m = top+8 (W2k), = top+9 (XP: echte Mitte)
int texttop,bmtop;
SIZE textsize; // cy = 13 (W2k,XP)
GetTextExtentPoint32(dis->hDC,KARTECLASSNAME,1,&textsize); // anscheinend für XP-Look so erforderlich
texttop=m-(textsize.cy>>1);
bmtop=m-7;
SetBkMode(dis->hDC,TRANSPARENT); // (undokumentiert wie Windows die übrigen Strings positioniert!)
switch (dis->itemID) {
case 2: if (K->HoverStaat>=0) { // Land mit Flagge
TCHAR s[64];
int l=LoadString(ghInstance,352+K->HoverStaat,s,elemof(s));
ImageList_Draw(K->flags,K->HoverStaat,dis->hDC,dis->rcItem.left+1,bmtop+1,0);
ExtTextOut(dis->hDC,dis->rcItem.left+20,texttop,ETO_CLIPPED,&dis->rcItem,s,l,NULL);
}break;
case 4: { // Warnung mit Achtungssymbol
DrawIconEx(dis->hDC,dis->rcItem.left,bmtop,LoadIcon(0,IDI_EXCLAMATION),16,16,0,0,DI_NORMAL);
ExtTextOut(dis->hDC,dis->rcItem.left+20,texttop,ETO_CLIPPED,&dis->rcItem,
K->strStatus4,lstrlen(K->strStatus4),NULL);
}break;
}
}break;
case WM_CONTEXTMENU: {
HMENU m=CreateMenu();
HMENU sm=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
if (K->kShow&0x10) { // bei sichtbarer Menüzeile Kontextmenü ausdünnen
DeleteMenu(sm,0,MF_BYPOSITION); // Kein "Ansicht"
DeleteMenu(sm,0,MF_BYPOSITION); // Kein "Miniatur"
DeleteMenu(sm,1,MF_BYPOSITION); // Keine "Skalierung"
DeleteMenu(sm,3,MF_BYPOSITION); // Keine "Hilfe"
}
PrepareMenu(K,sm);
AddBitmaps(GetSubMenu(sm,1),false);
EnableMenuItem(sm,0x81,Config.uRegion>=0&&K->HoverIndex>=0&&K->HoverIndex!=Config.uRegion?MF_ENABLED:MF_GRAYED); // Eigener Position Vorhersageregion zuordnen
EnableMenuItem(sm,0x82,Config.uRegion>=0?MF_ENABLED:MF_GRAYED); // Eigene Position löschen
InsertMenu(m,0,MF_BYPOSITION|MF_POPUP,(UINT)sm,NULL); // sonst geht's nicht! Warum auch immer.
TrackPopupMenu(sm,TPM_RIGHTBUTTON,
GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),0,Wnd,NULL);
DestroyMenu(m);
DelBitmaps(false);
}break;
case WM_SYSCOLORCHANGE: if (K->kShow&0x10) { // schließt WM_THEMECHANGED ein
BitmapUsage=-BitmapUsage;
DelBitmaps(true); // <true> ist eigentlich nur für das erste informierte Kartenfenster richtig
AddBitmaps(GetSubMenu(GetMenu(Wnd),4),true);
}break;
case WM_COMMAND: switch (wParam&0xF0) {
case 0x10: kShowChange(K,(BYTE)(1<<(wParam&7))); break; // Menüzeile, Statuszeile usw.
case 0x20: switch (wParam&0x0C) {
case 0: kMiniSet(K,(BYTE)wParam,0x03); break;
case 4: kMiniSet(K,(BYTE)(wParam<<2),0x0C); break;
case 8: {
char b=K->kMini;
switch (wParam&3) {
case 0: kMiniSet(K,(char)(b^0x10),-1); break;
case 2: kMiniSet(K,(char)(b+0x20),-1); break;
case 3: kMiniSet(K,(char)(b-0x20),-1); break;
}
}break;
}break;
case 0x30: kVisChange(K,(BYTE)(1<<(wParam&7))); break;
case 0x40: switch (wParam&15) {
case 0: SetScale(K,SB_PAGELEFT); break;
case 1: SetScale(K,SB_LINELEFT); break;
case 2: SetScale(K,SB_LINERIGHT); break;
case 3: SetScale(K,SB_PAGERIGHT); break;
case 4: SetScale(K,100); break; // 100%
case 5: SetScale(K,66); break; // einpassen
}break;
case 0x50: switch (wParam&0x0C) {
case 0: switch (wParam&3) {
case 0:
case 1: {
int kode = -(K->kAutoScale^1<<(wParam&15));
if (!kode) kode=42;
SetScale(K,kode);
}break;
case 2: kAutoScaleSet(K,0,0,0x04); break;
case 3: kAutoScaleSet(K,0,0,0x08); break;
}break;
case 0x08: kAutoScaleSet(K,(BYTE)(wParam<<4),0x30,0); break;
case 0x0C: kAutoScaleSet(K,(BYTE)(wParam<<6),0xC0,0); break;
}break;
case 0x60: kWetterSet(K,(BYTE)wParam,0x0F,0); break;
case 0x70: if (wParam&4) {
kWetterSet(K,0,0,(BYTE)(0x40<<(wParam&1)));
}else{
kWetterSet(K,(BYTE)((wParam&3)<<4),0x30,0);
}break;
case 0x80: switch (wParam&15) { // nur vom Kontextmenü
case 0: { // Eigene Position hier setzen
int k;
PointF geo={cp2bpf(K,K->MousePos.x,SB_HORZ),cp2bpf(K,K->MousePos.y,SB_VERT)};
geo=MapMercator(K->kInfo->kUnmap(geo)); // Mercator-Pixel
Config.uPos.x=(short)lrint(geo.X);
Config.uPos.y=(short)lrint(geo.Y);
k=K->HoverIndex;
if (k<0) k=FindNearestCity(K,
cp2bp(K,K->MousePos.x,SB_HORZ),
cp2bp(K,K->MousePos.y,SB_VERT)); // sollte >=0 liefern
Config.uRegion=(char)k;
CalcMyPos(K,false); // nur MyPosStaat ermitteln
InfoPropSheet(19,0); // Alle Karten (auch die eigene) müssen aktualisiert werden
}break;
case 1: { // Diese Vorhersageregion der eigenen Position zuordnen
int k=SearchHoverIndex(K);
if (k>=0) Config.uRegion=k;
InfoPropSheet(19,1); // "Wetter"-Dialog aktualisieren
}break;
case 2: { // Eigene Position löschen
if (Config.Region==90) Config.Region=Config.uRegion; // ggf. ändern!
Config.uRegion=-1;
InfoPropSheet(19,2); // Alle Karten müssen aktualisiert werden
}break;
}break;
case 0x90: {
if (ChmHelp) HtmlHelp(Wnd,HelpName,HH_HELP_CONTEXT,wParam);
else WinHelp(Wnd,HelpName,HELP_CONTEXT,wParam);
}break;
}break;
case WM_NOTIFY: {
LPNMHDR hdr=(LPNMHDR)lParam;
if (hdr->idFrom>=0x60) switch (hdr->code) { // Toolbar
case TTN_GETDISPINFO: {
LPNMTTDISPINFO di=(void*)lParam;
HMENU m=LoadMenu(ghInstance,MAKEINTRESOURCE(48));
if (0x70<=hdr->idFrom && hdr->idFrom<0x74) {
TagesName((int)hdr->idFrom-0x70,K->strTip,elemof(K->strTip),0);
}else{
GetMenuString(m,(UINT)hdr->idFrom,K->strTip,elemof(K->strTip),0);
StripAmpersand(K->strTip);
}
DestroyMenu(m);
di->lpszText=K->strTip;
}break;
}else switch (hdr->code) {
case TTN_SHOW: return 0;
case TTN_GETDISPINFO: { // == TTN_NEEDTEXT, für fliegenden Tooltip
PTSTR a=K->strTip,e=a+elemof(K->strTip)-1; // -1 = Platz für '\n'
LPNMTTDISPINFO di=(void*)lParam;
int l=0;
di->lpszText=a;
a+=MkGeoStr(K,a,(int)(e-a));
if (K->HoverStaat>=0) { // Ländergrenzen
TCHAR t[256], s[64];
LoadString(ghInstance,352+K->HoverStaat,s,elemof(s));
LoadString(ghInstance,67,t,elemof(t)); // "Staat: %d" (wie in Statuszeile)
*a++='\n';
a+=wnsprintf(a,(int)(e-a),t,s);
}
if (K->HoverIndex>=0) { // Vorhersageregion
TCHAR t[256], s[128], *stadt;
s[LoadString(ghInstance,256+K->HoverIndex,s,elemof(s)-1)+1]=0; // doppelt terminieren
stadt=s+lstrlen(s)+1;
LoadString(ghInstance,70,t,elemof(t)); // Kombinations-String (4 Teile)
*a++='\n';
a+=wnsprintf(a,(int)(e-a),t,s);
if (*stadt) {
*a++='\n';
a+=wnsprintf(a,(int)(e-a),
GetStr(t,K->map[K->HoverIndex]->CitySize>3 ? 1/*Stadt*/ : 2/*Ort*/),stadt);
}
if (K->HoverIndex<90) {
U64 v;
LoadString(ghInstance,352+Flaggen[K->HoverIndex],s,elemof(s));
*a++=' ';
a+=wnsprintf(a,(int)(e-a),GetStr(t,3),s);
v=GetTagWetter(K->kWetter,K->HoverIndex);
if ((BYTE)0xE6>>(K->kWetter&7)&1) a+=SchwerwetterText(a,(int)(e-a),false,v,false);
if ((BYTE)0xF8>>(K->kWetter&7)&1) a+=SchwerwetterText(a,(int)(e-a),true,v,false);
} // TODO: Tag/Nacht-Unterscheidung wenn beides
}
}break;
}
}break;
case WM_CLOSE: {
if (Wnd==hFirst && PropWnd) {
if (Config.Checkmarks&0x80) {
HWND w=hFirst; // temporär verschwinden lassen
hFirst=0;
PropRespawn(); // ohne Owner (wieder) erzeugen
hFirst=w; // nachher setzen
}else{
HMENU m=GetSystemMenu(PropWnd,FALSE); // Menüpunkt entfernen
DeleteMenu(m,0x10,0); // bRevert=TRUE geht nicht! (Das generiert das „lange“ Systemmenü)
}
}
}break;
case WM_DESTROY: {
int i;
if (Wnd==hFirst) {
WINDOWPLACEMENT wp;
wp.length=sizeof(wp);
GetWindowPlacement(Wnd,&wp);
Config.kShow=Config.kShow&0xFC|wp.showCmd&3; // Ob das klappt?
if ((wp.showCmd&3)==1) { // nur bei "normal" Position speichern
Config.kPos.left =(short)wp.rcNormalPosition.left;
Config.kPos.top =(short)wp.rcNormalPosition.top;
Config.kPos.right =(short)wp.rcNormalPosition.right;
Config.kPos.bottom=(short)wp.rcNormalPosition.bottom;
}
Config.kScrl.x=(short)K->ScrollInfo[SB_HORZ].nPos;
Config.kScrl.y=(short)K->ScrollInfo[SB_VERT].nPos;
hFirst=0;
}
if (K->pPicture) {
if (K->bPictureHBM) DeleteBitmap(K->hPicture);
else (*(IPictureVtbl**)K->pPicture)->Release(K->pPicture);
}
DeleteGdiHandles();
if (K->MiniBmp) DeleteBitmap(K->MiniBmp);
for (i=0; i<NUMSTAAT; i++) if (K->staat[i]) DeleteRgn(K->staat[i]);
if (GetMenu(Wnd)) DelBitmaps(false);
ImageList_Destroy(K->flags);
}break;
case WM_NCDESTROY: {
LocalFree(K);
}return 0;
}
/*** (D) Nachrichtenbehandlung für das Karten-Fenster, egal ob Child oder Top-Level ***/
if (Wnd==K->hMap) switch (msg) {
case WM_CREATE: {
GetCursorPos(&K->MousePos);
ScreenToClient(Wnd,&K->MousePos);
}break;
case WM_HSCROLL: HandleHScroll(K,(char)wParam); break;
case WM_VSCROLL: HandleVScroll(K,(char)wParam); break;
case WM_SIZE: {
if (!K->bInitialized) break; // noch nicht initialisiert
if (++K->Recursion<=2) {
LoadClientRect(Wnd,&K->ScrollRect);
if (K->kAutoScale&3) SetScale(K,-(K->kAutoScale&3));
else AdjustScrollRange(K); // kann erneut WM_SIZE auslösen!
}
K->Recursion--;
if (!K->Recursion && K->kMini&3) {
MiniParentChange(K); // Mini-Fenster ggf. repositionieren
ShowMini(K);
InvalidateRect(K->hMini,NULL,FALSE);
}
}break;
// Mausereignisse (ggf. mit Capture)
case WM_LBUTTONDOWN: { // linke Maustaste wählt permanente Vorhersageregion aus
K->MousePos.x=GET_X_LPARAM(lParam); // Hier immer nachführen,
K->MousePos.y=GET_Y_LPARAM(lParam); // sonst unerwartete Schiebeoperation beim Schließen von Menüs
K->bShifted=false;
}break;
case WM_LBUTTONDBLCLK: {
if (!K->bShifted) PropShowWetter();
}break;
case WM_LBUTTONUP: if (!K->bShifted) {
#ifdef POLYEDIT
savePoint(K);
#else
SetRegion((char)SearchHoverIndex(K));
#endif
}break;
case WM_MOUSEMOVE: {
TRACKMOUSEEVENT tme={sizeof(tme),TME_LEAVE,Wnd,100};
TrackMouseEvent(&tme);
// Nach WM_MOUSEACTIVATE und WM_LBUTTONDOWN kommt immer ein WM_MOUSEMOVE ohne tatsächlichen Versatz
if (wParam&MK_LBUTTON
&& HandleShift(K,GET_X_LPARAM(lParam)-K->MousePos.x,GET_Y_LPARAM(lParam)-K->MousePos.y))
K->bShifted=true;
K->MousePos.x=GET_X_LPARAM(lParam); // immer nachführen
K->MousePos.y=GET_Y_LPARAM(lParam);
MousePosChanged(K);
if (K->kShow&0x80) {
DWORD pt=ClientToScreenS(Wnd,(DWORD)lParam)+0x180018;
SendMessage(K->hTip,TTM_TRACKPOSITION,0,pt);
TrackActivate(K,TRUE);
}
}break;
case WM_MOUSELEAVE: {
if (K->kShow&0x40) SendMessage(K->hStatus,SB_SETTEXT,1,0); // StatusGeo: Kein Text
SetHoverStaat(K,-1);
SetHoverIndex(K,-1);
if (K->kShow&0x80) TrackActivate(K,FALSE);
}break;
case WM_ERASEBKGND: {
LoadClientRect(Wnd,&K->ScrollRect); // WM_SIZE kommt _nach_ WM_ERASEBKGND (Win7-64)
EraseOutside(K,(HDC)wParam);
}return 1;
case WM_PRINTCLIENT: {
DrawAll(K,(HDC)wParam,&K->ScrollRect);
}return 0;
case WM_PAINT: {
PAINTSTRUCT ps;
BeginPaint(Wnd,&ps);
DrawAll(K,ps.hdc,&ps.rcPaint);
EndPaint(Wnd,&ps);
}return 0;
// Fokussierte Ereignisse:
case WM_MOUSEWHEEL: {
i=(short)HIWORD(wParam); // hier: WHEEL_DELTA nicht auswerten oder akkumulieren
SetScale(K,i>=0?SB_LINERIGHT:SB_LINELEFT);
}break;
case WM_KEYDOWN: switch ((BYTE)wParam) {
case VK_LEFT: HandleHScroll(K,SB_LINELEFT); break;
case VK_RIGHT: HandleHScroll(K,SB_LINERIGHT); break;
case VK_UP: HandleVScroll(K,SB_LINELEFT); break;
case VK_DOWN: HandleVScroll(K,SB_LINERIGHT); break;
case VK_PRIOR: HandleVScroll(K,SB_PAGERIGHT); break;
case VK_NEXT: HandleVScroll(K,SB_PAGELEFT); break;
case VK_HOME: HandleHScroll(K,SB_PAGERIGHT); break;
case VK_END: HandleHScroll(K,SB_PAGELEFT); break;
case VK_TAB: HandleTab(K,0); break;
case VK_DELETE: if (Config.uRegion>=0) SendMessage(K->hMain,WM_COMMAND,0x82,0); break;
case VK_RETURN: if (K->HoverIndex==Config.Region || K->HoverIndex<0) {
PropShowWetter();
}else{
SetRegion(K->HoverIndex);
}break;
#ifdef POLYEDIT
case VK_BACK: saveUndo(K); break;
#endif
}break;
case WM_CHAR: if (IsCharAlpha((TCHAR)wParam)) HandleTab(K,(TCHAR)wParam);
else{
switch ((int)wParam) {
case ' ': { // Wetteranzeige durchschalten
BYTE b=K->kWetter&0x3F;
BYTE start=b;
char dir=GetKeyState(VK_SHIFT)<0?-1:1; // UMSCH+Leertaste = rückwärts
for(;;){
b+=dir;
b&=0x3F;
if (AnySymbol(b)) break; // Nicht piepsen wenn genau eine Seite mit Symbolen existiert
if (b==start) {MessageBeep(MB_ICONSTOP); break;}
}
if (b!=start) kWetterSet(K,b,0x3F,0);
}break;
case '1':
case '2':
case '3':
case '4': kWetterSet(K,(BYTE)((wParam-'1')<<4),0x30,0); break;
case '5':
case '6':
case '7':
case '8':
case '9': kWetterSet(K,(BYTE)(wParam-'5'+1),0x0F,0); break;
case '0': kWetterSet(K,0,0x0F,0); break;
case '#': kWetterSet(K,6,0x0F,0); break;
case '^': kWetterSet(K,7,0x0F,0); break;
case '+': SetScale(K,SB_LINERIGHT); break; // ACDSee-kompatibel
case '-': SetScale(K,SB_LINELEFT); break;
case '/': SetScale(K,100); break; // 100 = Magic für 100%
case '*': SetScale(K,66); break; // 66 = Magic für "an Fenstergröße anpassen"
#ifdef POLYEDIT
case 27: saveEnd(K); break;
#endif
}
if ((int)wParam==sDecimal[0]) kWetterSet(K,6,0x0F,0); break;
if ((int)wParam==(sDecimal[0]^('.'^','))) kWetterSet(K,7,0x0F,0); break;
}break;
}
return DefWindowProc(Wnd,msg,wParam,lParam);
}
/******************************
* Initialisierung und Aufruf *
******************************/
void RegisterKarte() {
WNDCLASSEX wc={
sizeof(wc),
CS_DBLCLKS,
MainWndProc,
0,
sizeof(karte*),
ghThisInst,
LoadIcon(ghThisInst,MAKEINTRESOURCE(100)),
LoadCursor(0,IDC_ARROW),
0,
NULL,
KARTECLASSNAME};
RegisterClassEx(&wc);
#ifndef UNICODE
{char s[10]; // Hintertür für manche Win98
NoAlpha=(bool)GetEnvironmentVariable("NoAlpha",s,elemof(s));
}
#endif
}
HWND Karte() {
TCHAR s[64],*p1,*p2;
if (GetKeyState(VK_SHIFT)>=0 && hFirst) {
if (IsIconic(hFirst)) ShowWindow(hFirst,SW_RESTORE); // In diesem Fall klappt alles
SetActiveWindow(hFirst); // Trotzdem bekommt das Fenster keinen Fokus (Win2k)
BringWindowToTop(hFirst); // Immerhin kommt es hoch (Win2k)
return hFirst;
}
LoadString(ghInstance,64,s,elemof(s));
p1=GetStr(s,1)-1;
p2=GetStr(s,2)-1;
if (hFirst) *p1=' '; // "[sekundär]" erscheinen lassen
if (!IsUserAdmin()) {
*p2=' '; // "¶" erscheinen lassen - als Ersatz für ein Negativ-Smiley
if (!hFirst) lstrcpy(p1,p2); // vorkopieren
}
return CreateWindow(KARTECLASSNAME,s,WS_OVERLAPPEDWINDOW|WS_VISIBLE|WS_CLIPCHILDREN,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
0,0,ghThisInst,NULL);
}
Detected encoding: ANSI (CP1252) | 4
|
|