/* \file cprofile.c * Track profile */ /* XTrkCad - Model Railroad CAD * Copyright (C) 2005 Dave Bullis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "custom.h" #include "cselect.h" #include "cundo.h" #include "layout.h" #include "param.h" #include "shrtpath.h" #include "track.h" #include "common-ui.h" /* PROFILE COMMAND TEST CASE (use 0testprof.xtc - 6 tracks connected 0:0:1 1:0:1 2:0:1 3:0:1 4:0:1 5:0:1 6:0:1) PreCond Action PostCond / empty -> creating single pt A1 - - 10 10 - A2 - - 20 20 11 A3 - - 11 11 20 / single pt -> delete B1 10 - 10 - - B2 20 11 20 - - B3 20 11 11 - - / single pt at EOT - extend C1 10 - 11 10 11 {1} C2 10 - 20 10 11 {1} C3 10 - 41 10 41 {1234} C4 10 - 50 10 41 {1234} / single pt at mid track - extend D1 31 40 11 31 20 {32} D2 31 40 20 31 20 {32} D3 31 40 51 40 51 {45} D4 31 40 61 40 61 {456} D5 31 40 10 31 10 {321} / length=2, delete end E1 30 41 30 40 41 {4} E2 30 41 21 40 41 {4} E3 30 41 41 30 31 {3} E4 30 41 50 30 31 {3} / length=1, delete end F1 30 31 30 31 - F2 30 31 21 31 - F3 30 31 31 30 - F4 30 31 40 30 - / length=1, extend G1 30 31 11 20 31 {23} G2 30 31 10 10 31 {123} G3 30 31 51 30 51 {345} G4 30 31 60 30 51 {345} G5 30 31 61 30 61 {3456} / length=2, extend H1 30 41 11 20 41 {234} H2 30 41 10 10 41 {1234} H3 30 41 51 30 51 {345} H4 30 41 60 30 51 {345} H5 30 41 61 30 61 {3456} */ /***************************************************************************** * * PROFILE WINDOW * */ static wDrawColor profileColorDefinedProfile; static wDrawColor profileColorUndefinedProfile; static wDrawColor profileColorFill; static wFontSize_t screenProfileFontSize = 12; static wFontSize_t printProfileFontSize = 6; static BOOL_T printVert = TRUE; static wMenu_p profilePopupM; static track_p profilePopupTrk; static EPINX_T profilePopupEp; static wMenuToggle_p profilePopupToggles[3]; static int log_profile = 0; #define LABELH (labelH*fontSize/screenProfileFontSize) #define LABELW (labelW*fontSize/screenProfileFontSize) #define PBB(FS) (2.0*(labelH*(FS)/screenProfileFontSize+3.0/mainD.dpi)) #define PBT (10.0/mainD.dpi) #define PBR(FS) (1.0*(labelW*(FS)/screenProfileFontSize+3.0/mainD.dpi)) #define PBL(FS) (1.0*(labelW*(FS)/screenProfileFontSize+3.0/mainD.dpi)) static FLOAT_T labelH; static FLOAT_T labelW; track_p pathStartTrk; EPINX_T pathStartEp; track_p pathEndTrk; EPINX_T pathEndEp; #define PASSERT( F, X, R ) if ( ! (X) ) { ErrorMessage( MSG_PASSERT, F, __LINE__, #X ); return R; } #define NOP typedef struct { track_p trk; EPINX_T ep; DIST_T elev; DIST_T dist; BOOL_T defined; /* from prev PE to current */ } profElem_t, *profElem_p; static dynArr_t profElem_da; static profElem_p copyOfprofElem; #define profElem(N) DYNARR_N( profElem_t, profElem_da, N ) typedef struct { DIST_T dist; char * name; } station_t, *station_p; static dynArr_t station_da; #define station(N) DYNARR_N( station_t, station_da, N ) struct { DIST_T totalD, minE; int minC, maxC, incrC; DIST_T scaleX, scaleY; } prof; /** * Creates a copy of profile elements */ static void CreateCopyProfileElements() { if (copyOfprofElem) { MyFree(copyOfprofElem); } copyOfprofElem = MyMalloc(profElem_da.cnt * sizeof(profElem_t)); for (int i = 0; i < profElem_da.cnt; i++) { copyOfprofElem[i] = profElem(i); } } /** * Destroys the copy of profile elements */ static void DestroyCopyOfProfileElements() { if (copyOfprofElem) { MyFree(copyOfprofElem); copyOfprofElem = NULL; } } /** * Draw profile * * \param D The drawCmd_p to use. * \param fontSize Size of the font. * \param printVert print vertical. */ static void DrawProfile(drawCmd_p D, wFontSize_t fontSize, BOOL_T printVert) { coOrd pl, pt, pb; int inx; DIST_T grade; wFont_p fp; static dynArr_t points_da; #define points(N) DYNARR_N( coOrd, points_da, N ) wDrawWidth lw; station_p ps; coOrd textsize; lw = (wDrawWidth)(D->dpi*1.0/mainD.dpi); fp = wStandardFont(F_HELV, FALSE, FALSE); DYNARR_RESET(pts_t, points_da); pb.x = pt.x = 0; pb.y = prof.minE; pt.y = GetDim(prof.maxC); DrawLine(D, pb, pt, 0, snapGridColor); pb.x = pt.x = prof.totalD; DrawLine(D, pb, pt, 0, snapGridColor); pb.x = 0; pt.x = prof.totalD; // Draw horizontal grid and y scale for (inx=prof.minC; inx<=prof.maxC; inx+=prof.incrC) { coOrd textsize; // grid line pt.y = pb.y = GetDim(inx); DrawLine(D, pb, pt, 0, snapGridColor); // scale sprintf(message, "%d", inx); DrawTextSize(&mainD, message, wStandardFont(F_HELV, FALSE, FALSE), screenProfileFontSize, FALSE, &textsize); pl.x = ((-3.0/mainD.dpi) - textsize.y*0.5 - textsize.x) / prof.scaleX*D->scale; pl.y = pb.y-LABELH/2/prof.scaleY*D->scale; DrawString(D, pl, 0.0, message, fp, fontSize*D->scale, borderColor); } // show the measurement units sprintf(message, "%s", units == UNITS_ENGLISH ? "in." : "cm"); DrawTextSize(&mainD, message, wStandardFont(F_HELV, FALSE, FALSE), screenProfileFontSize, FALSE, &textsize); pl.x = ((-3.0 / mainD.dpi) - textsize.y*0.5 - textsize.x) / prof.scaleX*D->scale; pl.y += LABELH * 1.5 / prof.scaleY*D->scale; DrawString(D, pl, 0.0, message, fp, fontSize*D->scale, borderColor); if (profElem_da.cnt <= 0) { return; } for (inx=0; inx 1) { DYNARR_APPEND(coOrd, points_da, 10); pt.x = prof.totalD; points(points_da.cnt-1) = pt; DYNARR_APPEND(pts_t, points_da, 10); pb.x = 0; points(points_da.cnt-1) = pb; DrawPoly(D, points_da.cnt, &points(0), NULL, profileColorFill, 1, DRAW_FILL); } pt.y = prof.minE-(2*LABELH+3.0/mainD.dpi)/prof.scaleY*D->scale; for (inx=0; inxname, fp, fontSize, FALSE, &textsize); pt.x = ps->dist - textsize.x/2.0/prof.scaleX*D->scale; if (pt.x < -PBR(screenProfileFontSize)) { pt.x = -(PBR(screenProfileFontSize)-3/mainD.dpi)/prof.scaleX*D->scale; } else if (pt.x+textsize.x > prof.totalD) { pt.x = prof.totalD-(textsize.x-3/mainD.dpi)/prof.scaleX*D->scale; } DrawString(D, pt, 0.0, ps->name, fp, fontSize*D->scale, borderColor); } pb.x = 0.0; pb.y = prof.minE; // mark the starting point for the profile pt = points(0); DrawLine(D, pb, pt, lw, snapGridColor); DrawArc(D, pt, 0.05, 0, 360, TRUE, 2, wDrawColorGrey40); if (units==UNITS_ENGLISH) { sprintf(message, "%0.1f", PutDim(profElem(0).elev)+0.05); } else { sprintf(message, "%0.1f", PutDim(profElem(0).elev)+0.05); } if (printVert) { pl.x = pt.x + LABELH/2.0/prof.scaleX*D->scale; pl.y = pt.y + 2.0/mainD.dpi/prof.scaleY*D->scale + GetDim(prof.incrC) / 16;; DrawString(D, pl, 270.0, message, fp, fontSize*D->scale, borderColor); } else { pl.x = pt.x+2.0/mainD.dpi/prof.scaleX*D->scale + GetDim(prof.incrC) / 16;; pl.y = pt.y; if (profElem_da.cnt>1 && profElem(0).elev < profElem(1).elev) { pl.y -= LABELH/prof.scaleY*D->scale; } DrawString(D, pl, 0.0, message, fp, fontSize*D->scale, borderColor); } pl = pt; for (inx=1; inxoptions; D->options = D->options | DC_DOT; DrawLine(D, pb, pt, lw, borderColor); D->options = oldOptions; } // draw grade line DrawLine(D, pl, pt, lw*2, (profElem(inx).defined ? profileColorDefinedProfile : profileColorUndefinedProfile)); // draw the markers DrawArc(D, pt, 0.05, 0, 360, TRUE, 2, wDrawColorGrey40); if (profElem(inx).dist > 0.1) { grade = fabs(profElem(inx).elev-profElem(inx-1).elev)/ (profElem(inx).dist-profElem(inx-1).dist); sprintf(message, "%0.1f%%", round(grade*1000.0)/10.0); DrawTextSize(&mainD, message, fp, fontSize, FALSE, &textsize); pl.x = (points(inx).x+points(inx-1).x)/2.0; pl.y = (points(inx).y+points(inx-1).y)/2.0; if (printVert) { pl.x += (LABELH/2)/prof.scaleX*D->scale; pl.y += ((LABELH/2)*grade/prof.scaleX + 2.0/mainD.dpi/prof.scaleY)*D->scale; DrawString(D, pl, 270.0, message, fp, fontSize*D->scale, borderColor); } else { pl.x -= (textsize.x/2)/prof.scaleX*D->scale; pl.y += (textsize.x/2)*grade/prof.scaleX*D->scale; DrawString(D, pl, 0.0, message, fp, fontSize*D->scale, borderColor); } } if (units==UNITS_ENGLISH) { if (prof.totalD > 240) { sprintf(message, "%0.1f'", (round((profElem(inx).dist/12.0)*10.0)/10.0)); } else { sprintf(message, "%d'%0.1f\"", (int)floor((profElem(inx).dist)/12.0), round(fmod(profElem(inx).dist,12.0)*10.0)/10.0); } } else { if (PutDim(prof.totalD) > 10000) { sprintf(message, "%0.1fm", (round(PutDim(profElem(inx).dist)/10.0)/10.0)); } else if (PutDim(prof.totalD) > 100) { sprintf(message, "%0.2fm", (round(PutDim(profElem(inx).dist))/100.0)); } else { sprintf(message, "%0.1fcm", round(PutDim(profElem(inx).dist)+0.5)); } } DrawTextSize(&mainD, message, fp, fontSize, FALSE, &textsize); pl.x = pb.x-(textsize.x/2)/prof.scaleX*D->scale; pl.y = prof.minE-(LABELH+3.0/mainD.dpi)/prof.scaleY*D->scale; DrawString(D, pl, 0.0, message, fp, fontSize*D->scale, borderColor); sprintf(message, "%0.1f", round(PutDim(profElem(inx).elev)*100.0)/100.0); if (printVert) { pl.x = pt.x + LABELH/2.0/prof.scaleX*D->scale; pl.y = pt.y + 2.0/mainD.dpi/prof.scaleY*D->scale+GetDim(prof.incrC) / 16; DrawString(D, pl, 270.0, message, fp, fontSize*D->scale, borderColor); } else { pl.x = pt.x + 2.0/mainD.dpi/prof.scaleX*D->scale + GetDim(prof.incrC) / 16; pl.y = pt.y; if (inx != profElem_da.cnt-1 && profElem(inx).elev < profElem(inx+1).elev) { pl.y -= LABELH/prof.scaleY*D->scale; } DrawString(D, pl, 0.0, message, fp, fontSize*D->scale, borderColor); } pl = pt; } } static void ProfilePix2CoOrd(drawCmd_p, wDrawPix_t, wDrawPix_t, coOrd *); static void ProfileCoOrd2Pix(drawCmd_p, coOrd, wDrawPix_t*, wDrawPix_t*); static drawCmd_t screenProfileD = { NULL, &screenDrawFuncs, DC_NOCLIP, 1.0, 0.0, {0.0,0.0}, {0.0,0.0}, ProfilePix2CoOrd, ProfileCoOrd2Pix }; static void ProfilePix2CoOrd( drawCmd_p d, wDrawPix_t xx, wDrawPix_t yy, coOrd * pos) { pos->x = (xx/d->dpi+d->orig.x)/prof.scaleX; pos->y = (yy/d->dpi+d->orig.y)/prof.scaleY+prof.minE; } static void ProfileCoOrd2Pix( drawCmd_p d, coOrd pos, wDrawPix_t *xx, wDrawPix_t *yy) { wDrawPix_t x, y; x = ((((pos.x*prof.scaleX)/d->scale-d->orig.x)*d->dpi+0.5)); y = (((((pos.y-prof.minE)*prof.scaleY)/d->scale-d->orig.y)*d->dpi+0.5)); if (d->angle == 0) { *xx = x; *yy = y; } else if (d->angle == -90.0) { /* L->P */ *xx = y; *yy = -x; } else { /* P->L */ *xx = -y; *yy = x; } } /** * Redraw profile window */ static void RedrawProfileW( wDraw_p d, void * context, wWinPix_t x, wWinPix_t y ) { wWinPix_t ww, hh; coOrd size; int divC; DIST_T maxE, rngE; profElem_t *p; wFont_p fp; POS_T w; coOrd textsize; char *pTestString; wDrawDelayUpdate(screenProfileD.d, TRUE); wDrawClear(screenProfileD.d); // get the size of the window area in pixels and convert to inches wDrawGetSize(screenProfileD.d, &ww, &hh); screenProfileD.size.x = (ww)/screenProfileD.dpi; screenProfileD.size.y = (hh)/screenProfileD.dpi; // calculate positions for labels??? fp = wStandardFont(F_HELV, FALSE, FALSE); screenProfileD.orig.x = -PBL(screenProfileFontSize); screenProfileD.orig.y = -PBB(screenProfileFontSize); /* Calculate usable dimension of canvas in inches */ size = screenProfileD.size; size.x -= (PBL(screenProfileFontSize)); size.y -= (PBB(screenProfileFontSize)); /* make sure there is enough space to show the rightmost coordinate value*/ if (units == UNITS_ENGLISH) { if (prof.totalD > 240.0) { pTestString = "9999'"; } else { pTestString = "999'11\""; } } else { if (PutDim(prof.totalD) > 10000.0) { pTestString = "999m"; } else { if (PutDim(prof.totalD) > 100.0) { pTestString = "99.9m"; } else { pTestString = "9.99m"; } } } DrawTextSize(&mainD, pTestString, fp, screenProfileFontSize, FALSE, &textsize); size.x -= textsize.x / 2; size.y -= textsize.y * 1.5 ; // now we have the size of the profile area #ifdef WINDOWS if (printVert) { size.x -= PBR(screenProfileFontSize)/4.0; size.y -= PBT; } else #endif { size.x -= PBR(screenProfileFontSize); size.y -= PBT; } if (size.x < 0.1 || size.y < 0.1) { wDrawDelayUpdate(screenProfileD.d, FALSE); return; } /* Calculate range of data values */ if (profElem_da.cnt<=0) { prof.totalD = 0.0; prof.minE = 0.0; maxE = 1.0; } else { maxE = prof.minE = profElem(0).elev; prof.totalD = profElem(profElem_da.cnt-1).dist; for (int inx=1; inxelevelev; } if (p->elev>maxE) { maxE = p->elev; } } } /* Calculate number of grid lines */ prof.minC = (int)floor(PutDim(prof.minE)); prof.maxC = (int)ceil(PutDim(maxE)); if (prof.maxC-prof.minC <= 0) { prof.maxC = prof.minC+1; } divC = (int)floor(size.y/labelH); if (divC < 1) { divC = 1; } prof.incrC = (prof.maxC-prof.minC+divC-1)/divC; if (prof.incrC < 1) { prof.incrC = 1; } prof.maxC = prof.minC + (prof.maxC-prof.minC+prof.incrC-1)/prof.incrC * prof.incrC; /* Reset bounds based on intergal values */ prof.minE = GetDim(prof.minC); rngE = GetDim(prof.maxC) - prof.minE; if (rngE < 1.0) { rngE = 1.0; } /* Compute vert scale */ prof.scaleY = size.y/rngE; sprintf(message, "%0.2f", maxE); DrawTextSize(&mainD, message, fp, screenProfileFontSize, FALSE, &textsize); w = textsize.x; w -= PBT; w += 4.0/screenProfileD.dpi; w -= (GetDim(prof.maxC)-maxE)*prof.scaleY; if (w > 0) { size.y -= w; prof.scaleY = size.y/rngE; } /* Compute horz scale */ if (prof.totalD <= 0.1) { prof.totalD = size.x; } prof.scaleX = size.x/prof.totalD; DrawProfile(&screenProfileD, screenProfileFontSize, #ifdef WINDOWS printVert #else FALSE #endif ); wDrawDelayUpdate(screenProfileD.d, FALSE); } static drawCmd_t printProfileD = { NULL, &printDrawFuncs, DC_PRINT | DC_NOCLIP, 1.0, 0.0, {0.0,0.0}, {1.0,1.0}, ProfilePix2CoOrd, ProfileCoOrd2Pix }; /** * This is the print function for the track height profile. The paper * orientation is based in on the orientation of the display windows. * Eg. is the windows is wider than high, the printout will be in * landscape. * \todo Rework the layout of the printout * * \param junk IN * \return */ static void DoProfilePrint(void * junk) { coOrd size, p[4]; int copies; WDOUBLE_T w, h, screenRatio, printRatio, titleH; wFont_p fp; coOrd screenSize; coOrd textsize; if (!wPrintDocStart(_("Profile"), 1, &copies)) { return; } printProfileD.d = wPrintPageStart(); if (printProfileD.d == NULL) { return; } printProfileD.dpi = wDrawGetDPI(printProfileD.d); wPrintGetPageSize(&w, &h); printProfileD.orig.x = -PBL(printProfileFontSize); printProfileD.orig.y = -PBB(printProfileFontSize); printProfileD.angle = 0.0; screenRatio = screenProfileD.size.y/screenProfileD.size.x; screenSize.x = prof.totalD*prof.scaleX; screenSize.y = GetDim(prof.maxC-prof.minC)*prof.scaleY; screenRatio = screenSize.y/screenSize.x; printProfileD.size.x = w; printProfileD.size.y = h; sprintf(message, _("%s Profile: %s"), sProdName, GetLayoutTitle()); fp = wStandardFont(F_TIMES, FALSE, FALSE); DrawTextSize(&mainD, message, fp, 24, FALSE, &textsize); titleH = textsize.y + 6.0/mainD.dpi; if (screenRatio < 1.0 && w < h) { /* Landscape -> Portrait */ printProfileD.angle = -90.0; printProfileD.orig.x += h; size.x = h; size.y = w; } else if (screenRatio > 1.0 && w > h) { /* Portrait -> Landscape */ printProfileD.angle = 90.0; printProfileD.orig.y += w; size.x = h; size.y = w; } else { size.x = w; size.y = h; } size.y -= titleH+(printVert?PBT*2:PBT)+PBB(printProfileFontSize); size.x -= 4.0/mainD.dpi+PBL(printProfileFontSize)+(printVert?PBR( printProfileFontSize)/4.0:PBR(printProfileFontSize)); printRatio = size.y/size.x; if (printRatio < screenRatio) { printProfileD.scale = screenSize.y/size.y; size.x = screenSize.x/printProfileD.scale; } else { printProfileD.scale = screenSize.x/size.x; printProfileD.orig.y -= size.y; size.y = screenSize.y/printProfileD.scale; printProfileD.orig.y += size.y; } #define PRINT_ABS2PAGEX(X) (((X)*printProfileD.scale)/prof.scaleX) #define PRINT_ABS2PAGEY(Y) (((Y)*printProfileD.scale)/prof.scaleY+prof.minE) p[0].y = PRINT_ABS2PAGEY(size.y+(printVert?PBT*2:PBT)+0.05); p[0].x = PRINT_ABS2PAGEX((size.x-textsize.x)/2.0); if (p[0].x < 0) { p[0].x = 0; } DrawString(&printProfileD, p[0], 0, message, fp, 24*printProfileD.scale, borderColor); p[0].x = p[3].x = PRINT_ABS2PAGEX((-PBL(printProfileFontSize))+2.0/mainD.dpi); p[0].y = p[1].y = PRINT_ABS2PAGEY(-PBB(printProfileFontSize)); p[1].x = p[2].x = PRINT_ABS2PAGEX(size.x+(printVert?PBR( printProfileFontSize)/4.0:PBR(printProfileFontSize))); p[2].y = p[3].y = PRINT_ABS2PAGEY(size.y+(printVert?PBT*2:PBT)); DrawPoly( &printProfileD, 4, p, NULL, drawColorBlack, 0, DRAW_CLOSED ); DrawProfile(&printProfileD, printProfileFontSize, printVert); wPrintPageEnd(printProfileD.d); wPrintDocEnd(); } /************************************************************************** * * Window Handlers * **************************************************************************/ static wWin_p profileW; static BOOL_T profileUndo = FALSE; static void DoProfileChange(void *junk); static void DoProfileReset(void *junk); static void DoProfileDone(void * junk); static void DoProfileClear(void * junk); static void DoProfilePrint(void * junk); static void DoProfileChangeMode(void * junk); static void SelProfileW(wIndex_t, coOrd); static void CloseProfileWindow(paramGroup_p pg, int event, void *data); static paramDrawData_t profileDrawData = { 300, 150, RedrawProfileW, SelProfileW, &screenProfileD }; static paramData_t profilePLs[] = { { PD_DRAW, NULL, "canvas", PDO_DLGRESIZE, &profileDrawData }, #define I_PROFILEMSG (1) { PD_MESSAGE, NULL, NULL, PDO_DLGIGNOREX, I2VP(300) }, #define I_CHANGEBUTTON 2 { PD_BUTTON, DoProfileChange, "change", PDO_DLGCMDBUTTON, NULL, N_("Change") }, #define I_RESETBUTTON 3 { PD_BUTTON, DoProfileReset, "reset", PDO_DLGCMDBUTTON, NULL, N_("Reset") }, #define I_CLEARBUTTON 4 { PD_BUTTON, DoProfileClear, "clear", PDO_DLGCMDBUTTON, NULL, N_("Clear") }, #define I_PRINTBUTTON 5 { PD_BUTTON, DoProfilePrint, "print", 0, NULL, N_("Print") } }; static paramGroup_t profilePG = { "profile", 0, profilePLs, COUNT( profilePLs ) }; #define CHANGEBUTTON ((wButton_p)profilePLs[I_CHANGEBUTTON].control) #define RESETBUTTON ((wButton_p)profilePLs[I_RESETBUTTON].control) #define CLEARBUTTON ((wButton_p)profilePLs[I_CLEARBUTTON].control) #define PRINTBUTTON ((wButton_p)profilePLs[I_PRINTBUTTON].control) static void SelProfileW( wIndex_t action, coOrd pos) { DIST_T dist; static DIST_T oldElev; static int inx; DIST_T elev; if (profElem_da.cnt <= 0) { return; } dist = pos.x; elev = pos.y; switch (action&0xFF) { case C_START: profileUndo = FALSE; break; case C_DOWN: for (inx=0; inx dist-profElem(inx-1).dist) { inx--; } break; } } if (inx >= profElem_da.cnt) { inx = profElem_da.cnt-1; } sprintf(message, _("Elev = %0.1f"), round(PutDim(elev)*10.0)/10.0); ParamLoadMessage(&profilePG, I_PROFILEMSG, message); oldElev = elev; RedrawProfileW( screenProfileD.d, NULL, 0, 0 ); break; case C_MOVE: if (inx < 0) { break; } if (profElem_da.cnt == 1) { sprintf(message, _("Elev = %0.1f"), round(PutDim(elev)*10.0)/10.0); } else if (inx == 0) { sprintf(message, _("Elev=%0.2f %0.1f%%"), round(PutDim(elev)*100.0)/100.0, round(fabs(((profElem(inx+1).elev-elev) / (profElem(inx+1).dist-profElem( inx).dist)) * 1000.0))/10.0); } else if (inx == profElem_da.cnt-1) { sprintf(message, _("%0.1f%% Elev = %0.2f"), round(fabs(((profElem(inx-1).elev-elev) / (profElem(inx).dist-profElem( inx-1).dist)) * 1000.0))/10.0, round(PutDim(elev)*100.0)/100.0); } else { sprintf(message, _("%0.1f%% Elev = %0.2f %0.1f%%"), round(fabs(((profElem(inx-1).elev-elev) / (profElem(inx).dist-profElem( inx-1).dist)) * 1000.0))/10.0, round(PutDim(elev)*100.0)/100.0, round(fabs((profElem(inx+1).elev-elev) / (profElem(inx+1).dist-profElem( inx).dist)) * 1000.0)/10.0); } ParamLoadMessage(&profilePG, I_PROFILEMSG, message); oldElev = elev; profElem(inx).elev = oldElev; RedrawProfileW( screenProfileD.d, NULL, 0, 0 ); wPause(500l); break; case C_UP: if (profileUndo == FALSE) { UndoStart(_("Profile Command"), "Profile - set elevation"); profileUndo = TRUE; } if (profElem(inx).trk) { UpdateTrkEndElev(profElem(inx).trk, profElem(inx).ep, ELEV_DEF|ELEV_VISIBLE, oldElev, NULL); } profElem(inx).elev = oldElev; RedrawProfileW( screenProfileD.d, NULL, 0, 0 ); ParamLoadMessage(&profilePG, I_PROFILEMSG, _("Drag to change Elevation")); inx = -1; break; default: break; } } static void HilightProfileElevations(BOOL_T show) { /*if ( profElem_da.cnt <= 0 ) {*/ HilightElevations(show); /*} else { }*/ } /** * * * \param pg The page. * \param event The event. * \param [in,out] data If non-null, the data. */ void CloseProfileWindow(paramGroup_p pg, int event, void *data) { Reset(); return; } /** * Undo the changes made in the profile window to the layout. */ static void ResetChanges() { if (copyOfprofElem) { for (int i = 0; i < profElem_da.cnt; i++) { profElem(i) = copyOfprofElem[i]; if (profElem(i).trk) { UpdateTrkEndElev(profElem(i).trk, profElem(i).ep, ELEV_DEF | ELEV_VISIBLE, copyOfprofElem[i].elev, NULL); } } } } /** * Executes the profile reset operation. All elevations are copied from the * backup, the main drawing area and the profile window are updated * * \param [in,out] junk */ static void DoProfileReset(void *junk) { if (profileUndo == 0) { profileUndo = TRUE; UndoStart(_("Profile Command"), "Profile"); } ResetChanges(); RedrawProfileW( screenProfileD.d, NULL, 0, 0 ); TempRedraw(); } /** * Confirm the changes made in the profile window * * \param [in,out] junk If non-null, the junk. */ static void DoProfileChange(void *junk) { DestroyCopyOfProfileElements(); TempRedraw(); } static void DoProfileDone(void * junk) { Reset(); } static void DoProfileClear(void * junk) { ResetChanges(); DYNARR_RESET( profElem_t, profElem_da ); DYNARR_RESET( station_t, station_da ); ClrAllTrkBitsRedraw(TB_PROFILEPATH, TRUE); pathStartTrk = pathEndTrk = NULL; RedrawProfileW( screenProfileD.d, NULL, 0, 0 ); } static void DoProfileChangeMode(void * junk) { if (profElem_da.cnt<=0) { InfoMessage(_("Select a Defined Elevation to start Profile")); } else { InfoMessage(_("Select a Defined Elevation to extend Profile")); } } /************************************************************************** * * Find Shortest Path * **************************************************************************/ static BOOL_T PathListEmpty(void) { return pathStartTrk == NULL; } static BOOL_T PathListSingle(void) { return pathStartTrk != NULL && (pathEndTrk == NULL || (GetTrkEndTrk(pathEndTrk,pathEndEp) == pathStartTrk && GetTrkEndTrk(pathStartTrk,pathStartEp) == pathEndTrk)); } static int profileShortestPathMatch; static DIST_T profileShortestPathDist; static int ProfileShortestPathFunc( SPTF_CMD cmd, track_p trk, EPINX_T ep, EPINX_T ep0, DIST_T dist, void * data) { int rc0=0; int pathMatch; switch (cmd) { track_p trkN; case SPTC_TERMINATE: rc0 = 1; break; case SPTC_MATCH: if (EndPtIsIgnoredElev(trk,ep)) { break; } if (PathListSingle()) { if (trk == pathStartTrk && ep == pathStartEp) { pathMatch = 2; } else if (trk == pathEndTrk && ep == pathEndEp) { pathMatch = 3; } else { break; } } else if ((trkN = GetTrkEndTrk(trk,ep)) == NULL) { break; } else { EPINX_T epN; epN = GetEndPtConnectedToMe(trkN, trk); if (trkN == pathStartTrk && epN == pathStartEp) { pathMatch = 1; } else if (trkN == pathEndTrk && epN == pathEndEp) { pathMatch = 2; } else if (trkN == pathStartTrk && trkN == pathEndTrk) { pathMatch = 2; } else if (trkN == pathStartTrk) { pathMatch = 1; } else if (trkN == pathEndTrk) { pathMatch = 2; } else { break; } } if (profileShortestPathMatch < 0 || profileShortestPathDist > dist) { LOG(log_shortPath, 4, (" Match=%d", pathMatch)) profileShortestPathMatch = pathMatch; profileShortestPathDist = dist; } rc0 = 1; break; case SPTC_MATCHANY: rc0 = -1; break; case SPTC_IGNNXTTRK: if (EndPtIsIgnoredElev(trk,ep)) { rc0 = 1; } else if ((GetTrkBits(trk)&TB_PROFILEPATH)!=0) { rc0 = 1; } else if ((!EndPtIsDefinedElev(trk,ep)) && GetTrkEndTrk(trk,ep)==NULL) { rc0 = 1; } else { rc0 = 0; } break; case SPTC_ADD_TRK: if (log_shortPath<=0|| logTable(log_shortPath).level<4) LOG(log_profile, 4, (" ADD_TRK T%d:%d", GetTrkIndex(trk), ep)) SetTrkBits(trk, TB_PROFILEPATH); DrawTrack(trk, &mainD, profilePathColor); rc0 = 0; break; case SPTC_VALID: rc0 = 1; break; default: break; } return rc0; } static int FindProfileShortestPath( track_p trkN, EPINX_T epN) { LOG(log_profile, 4, ("Searching from T%d:%d to T%d:%d or T%d:%d\n", GetTrkIndex(trkN), epN, pathStartTrk?GetTrkIndex(pathStartTrk):-1, pathStartTrk?pathStartEp:-1, pathEndTrk?GetTrkIndex(pathEndTrk):-1, pathEndTrk?pathEndEp:-1)) profileShortestPathMatch = -1; return FindShortestPath(trkN, epN, TRUE, ProfileShortestPathFunc, NULL); } /************************************************************************** * * Main Window Handler * **************************************************************************/ #define ONPATH_NOT (1<<0) #define ONPATH_END (1<<1) #define ONPATH_MID (1<<2) #define ONPATH_BRANCH (1<<3) static int OnPath(track_p trk, EPINX_T ep) { if (GetTrkBits(trk)&TB_PROFILEPATH) { track_p trk0; trk0 = GetTrkEndTrk(profilePopupTrk, profilePopupEp); if (trk0 && (GetTrkBits(trk0)&TB_PROFILEPATH)) { return ONPATH_MID; } if (trk == pathStartTrk && ep == pathStartEp) { return ONPATH_END; } return ONPATH_BRANCH; } return ONPATH_NOT; } static BOOL_T PathListCheck(void) { track_p trk; if (PathListEmpty() || PathListSingle()) { return TRUE; } if (!(GetTrkBits(pathStartTrk)&TB_PROFILEPATH)) { ErrorMessage(MSG_PST_NOT_ON_PATH); return FALSE; } if (!(GetTrkBits(pathEndTrk)&TB_PROFILEPATH)) { ErrorMessage(MSG_PET_NOT_ON_PATH); return FALSE; } trk = GetTrkEndTrk(pathStartTrk,pathStartEp); if (trk && (GetTrkBits(trk)&TB_PROFILEPATH)) { ErrorMessage(MSG_INV_PST_ON_PATH); return FALSE; } trk = GetTrkEndTrk(pathEndTrk,pathEndEp); if (trk && (GetTrkBits(trk)&TB_PROFILEPATH)) { ErrorMessage(MSG_INV_PET_ON_PATH); return FALSE; } return TRUE; } static void RemoveTracksFromPath( track_p *Rtrk, EPINX_T *Rep, track_p trkEnd, EPINX_T epEnd) { track_p trk = *Rtrk, trkN; EPINX_T ep = *Rep; PASSERT("removeTracksFromPath", trk, NOP); PASSERT("removeTracksFromPath", !PathListSingle(), NOP); while (1) { EPINX_T ep2; DrawTrack(trk, &mainD, drawColorWhite); ClrTrkBits(trk, TB_PROFILEPATH); DrawTrack(trk, &mainD, drawColorBlack); if (trk == trkEnd) { pathStartTrk = trkEnd; pathStartEp = epEnd; pathEndTrk = GetTrkEndTrk(pathStartTrk,pathStartEp); if (pathEndTrk) { pathEndEp = GetEndPtConnectedToMe(pathEndTrk,pathStartTrk); } return; } ep2 = GetNextTrkOnPath(trk, ep); PASSERT("removeTracksFromPath", ep2 >= 0,NOP); trkN = GetTrkEndTrk(trk,ep2); PASSERT("removeTracksFromPath", trkN != NULL, NOP); ep = GetEndPtConnectedToMe(trkN,trk); trk = trkN; if (EndPtIsDefinedElev(trk,ep)) { *Rtrk = trk; *Rep = ep; return; } } } static void ChkElev(track_p trk, EPINX_T ep, EPINX_T ep2, DIST_T dist, BOOL_T * defined) { profElem_p p; station_p s; EPINX_T epDefElev = -1; int mode; BOOL_T undefined; mode = GetTrkEndElevMode(trk, ep); if (mode == ELEV_DEF) { epDefElev = ep; } else if (mode == ELEV_STATION) { DYNARR_APPEND(station_t, station_da, 10); s = &station(station_da.cnt-1); s->dist = dist; s->name = GetTrkEndElevStation(trk,ep); } undefined = FALSE; if (epDefElev<0) { if ((trk == pathStartTrk && ep == pathStartEp) || (trk == pathEndTrk && ep == pathEndEp)) { epDefElev = ep; } } if (epDefElev<0) { if (ep == ep2 || GetTrkEndElevMode(trk,ep2) != ELEV_DEF) for (EPINX_T ep1=0; ep1=0) { DYNARR_APPEND(profElem_t, profElem_da, 10); p = &profElem(profElem_da.cnt-1); p->trk = trk; p->ep = epDefElev; p->dist = dist; if (GetTrkEndElevMode(trk,epDefElev) == ELEV_DEF) { p->elev = GetTrkEndElevHeight(trk,epDefElev); } else { ComputeElev(trk, epDefElev, TRUE, &p->elev, NULL, TRUE); } p->defined = *defined; *defined = TRUE; } else if (undefined) { *defined = FALSE; } } static void ComputeProfElem(void) { track_p trk = pathStartTrk, trkN; EPINX_T ep = pathStartEp, ep2; BOOL_T go; DIST_T dist; BOOL_T defined; DYNARR_RESET( profElem_t, profElem_da ); DYNARR_RESET( station_t, station_da ); dist = 0; defined = TRUE; if (PathListEmpty()) { return; } ChkElev(trk, ep, ep, dist, &defined); if (PathListSingle()) { return; } go = TRUE; while (go) { if (trk == pathEndTrk) { go = FALSE; ep2 = pathEndEp; } else { ep2 = GetNextTrkOnPath(trk, ep); //PASSERT( "computeProfElem", ep2 >= 0, NOP ); } dist += GetTrkLength(trk, ep, ep2); ChkElev(trk, ep2, ep, dist, &defined); if (!go) { break; } trkN = GetTrkEndTrk(trk,ep2); ep = GetEndPtConnectedToMe(trkN,trk); trk = trkN; } } static void DumpProfElems(void) { track_p trk, trkN; EPINX_T ep; BOOL_T go; trk = pathStartTrk; ep = pathStartEp; if (pathStartTrk==NULL) { lprintf("s--:- e--:-"); } else if (pathEndTrk == NULL) { lprintf("sT%d:%d e--:-", GetTrkIndex(pathStartTrk), pathStartEp); } else { lprintf("sT%d:%d eT%d:%d", GetTrkIndex(pathStartTrk), pathStartEp, GetTrkIndex(pathEndTrk), pathEndEp); } lprintf(" { "); go = TRUE; if (!PathListSingle()) while (trk) { EPINX_T ep2; if (trk==pathEndTrk) { ep2 = pathEndEp; go = FALSE; } else { ep2 = GetNextTrkOnPath(trk, ep); PASSERT("computeProfElem", ep2 >= 0, NOP); } lprintf("T%d:%d:%d ", GetTrkIndex(trk), ep, ep2); if (!go) { break; } trkN = GetTrkEndTrk(trk,ep2); ep = GetEndPtConnectedToMe(trkN,trk); trk = trkN; } lprintf("}"); } static void ProfileSelect(track_p trkN, EPINX_T epN) { track_p trkP; EPINX_T epP=-1; int rc; if (log_profile>=1) { DumpProfElems(); lprintf(" @ T%d:%d ", GetTrkIndex(trkN), epN); if (log_profile>=2) { lprintf("\n"); } } trkP = GetTrkEndTrk(trkN, epN); if (trkP) { epP = GetEndPtConnectedToMe(trkP, trkN); } if (!PathListCheck()) { return; } if (PathListEmpty()) { pathStartTrk = trkN; pathStartEp = epN; pathEndTrk = trkP; pathEndEp = epP; LOG(log_profile, 2, ("Adding first element\n")) } else if (PathListSingle() && ((trkN == pathStartTrk && epN == pathStartEp) || (trkP && trkP == pathStartTrk && epP == pathStartEp))) { pathStartTrk = pathEndTrk = NULL; LOG(log_profile, 2, ("Clearing list\n")) } else if ((trkN == pathStartTrk && epN == pathStartEp) || (trkP && trkP == pathStartTrk && epP == pathStartEp)) { RemoveTracksFromPath(&pathStartTrk, &pathStartEp, pathEndTrk, pathEndEp); LOG(log_profile, 2, ("Removing first element\n")) } else if ((trkN == pathEndTrk && epN == pathEndEp) || (trkP && trkP == pathEndTrk && epP == pathEndEp)) { RemoveTracksFromPath(&pathEndTrk, &pathEndEp, pathStartTrk, pathStartEp); LOG(log_profile, 2, ("Removing last element\n")) } else if ((GetTrkBits(trkN)&TB_PROFILEPATH) || (trkP && (GetTrkBits(trkP)&TB_PROFILEPATH))) { ErrorMessage(MSG_EP_ON_PATH); return; } else if ((rc = FindProfileShortestPath(trkN, epN)) > 0) { if (!(GetTrkBits(trkN)&TB_PROFILEPATH)) { PASSERT("profileSelect", trkP != NULL, NOP); trkN = trkP; epN = epP; LOG(log_profile, 2, ("Invert selected EP\n")) } switch (profileShortestPathMatch) { case 1: /* extend Start */ pathStartTrk = trkN; pathStartEp = epN; LOG(log_profile, 2, ("Prepending Path\n")) break; case 2: /* extend End */ pathEndTrk = trkN; pathEndEp = epN; LOG(log_profile, 2, ("Appending Path\n")) break; case 3: /* need to flip */ pathStartTrk = pathEndTrk; pathStartEp = pathEndEp; pathEndTrk = trkN; pathEndEp = epN; LOG(log_profile, 2, ("Flip/Appending Path\n")) break; default: CHECK(FALSE); } } else { ErrorMessage(MSG_NO_PATH_TO_EP); return; } DestroyCopyOfProfileElements(); ComputeProfElem(); CreateCopyProfileElements(); RedrawProfileW( screenProfileD.d, NULL, 0, 0 ); DoProfileChangeMode(NULL); if (log_profile>=1) { lprintf(" = "); DumpProfElems(); lprintf("\n"); } PathListCheck(); } static void ProfileSubCommand(void* pcmd) { long cmd = VP2L(pcmd); int mode; coOrd pos = oldMarker; DIST_T elev; DIST_T radius; if ((profilePopupTrk = OnTrack(&pos, TRUE, TRUE)) == NULL || (profilePopupEp = PickEndPoint(pos, profilePopupTrk)) < 0) { return; } if (profileUndo==0) { profileUndo = TRUE; UndoStart(_("Profile Command"), "Profile"); } radius = 0.05*mainD.scale; if (radius < trackGauge/2.0) { radius = trackGauge/2.0; } pos = GetTrkEndPos(profilePopupTrk, profilePopupEp); mode = GetTrkEndElevMode(profilePopupTrk, profilePopupEp); elev = 0.0; switch (cmd) { case 0: /* define */ ComputeElev(profilePopupTrk, profilePopupEp, TRUE, &elev, NULL, TRUE); mode = ELEV_DEF|ELEV_VISIBLE; break; case 1: /* ignore */ mode = ELEV_IGNORE|ELEV_VISIBLE; break; case 2: default: /* none */ mode = ELEV_NONE; break; } UpdateTrkEndElev(profilePopupTrk, profilePopupEp, mode, elev, NULL); ComputeProfElem(); RedrawProfileW( screenProfileD.d, NULL, 0, 0 ); TempRedraw(); // ProfileSubCommand } static STATUS_T CmdProfile(wAction_t action, coOrd pos) { track_p trk0; coOrd textsize; switch (action) { case C_START: if (profileW == NULL) { profileColorDefinedProfile = drawColorBlue; profileColorUndefinedProfile = drawColorRed; profileColorFill = drawColorGrey80; DrawTextSize(&mainD, "999.9", wStandardFont(F_HELV, FALSE, FALSE), screenProfileFontSize, FALSE, &textsize); labelH = textsize.y; labelW = textsize.x; profileW = ParamCreateDialog(&profilePG, MakeWindowTitle(_("Profile")), NULL, NULL, wHide, TRUE, NULL, F_RESIZE, CloseProfileWindow); } ParamLoadControls(&profilePG); ParamGroupRecord(&profilePG); wShow(profileW); ParamLoadMessage(&profilePG, I_PROFILEMSG, _("Drag to change Elevation")); DYNARR_RESET( profElem_t, profElem_da ); DYNARR_RESET( station_t, station_da ); RedrawProfileW( screenProfileD.d, NULL, 0, 0 ); ClrAllTrkBitsRedraw(TB_PROFILEPATH, TRUE); pathStartTrk = NULL; SetAllTrackSelect(FALSE); profileUndo = FALSE; InfoMessage(_("Select a Defined Elevation to start profile")); TempRedraw(); // CmdProfile C_START return C_CONTINUE; case C_LCLICK: InfoMessage(""); if ((trk0 = OnTrack(&pos, TRUE, TRUE)) != NULL) { EPINX_T ep0; ep0 = PickEndPoint(pos, trk0); if (ep0 >= 0) { ProfileSelect(trk0, ep0); } } return C_CONTINUE; case C_CMDMENU: if ((profilePopupTrk = OnTrack(&pos, TRUE, TRUE)) != NULL) { profilePopupEp = PickEndPoint(pos, profilePopupTrk); if (profilePopupEp >= 0) { int mode; mode = GetTrkEndElevMode(profilePopupTrk, profilePopupEp); if (mode != ELEV_DEF && mode != ELEV_IGNORE && mode != ELEV_NONE) { ErrorMessage(MSG_CHANGE_ELEV_MODE); } else { wMenuToggleEnable(profilePopupToggles[1], TRUE); if (OnPath(profilePopupTrk, profilePopupEp) & (ONPATH_END|ONPATH_MID)) { wMenuToggleEnable(profilePopupToggles[1], FALSE); } wMenuToggleSet(profilePopupToggles[0], mode == ELEV_DEF); wMenuToggleSet(profilePopupToggles[1], mode == ELEV_IGNORE); wMenuToggleSet(profilePopupToggles[2], mode == ELEV_NONE); menuPos = pos; wMenuPopupShow(profilePopupM); } } } return C_CONTINUE; case C_OK: DoProfileDone(NULL); return C_TERMINATE; case C_CANCEL: wHide(profileW); ClrAllTrkBitsRedraw(TB_PROFILEPATH, TRUE); return C_TERMINATE; case C_REDRAW: if (wWinIsVisible(profileW)) { HilightProfileElevations(wWinIsVisible(profileW)); } return C_CONTINUE; } return C_CONTINUE; } static void ProfileChange(long changes) { if ((changes & CHANGE_UNITS) && screenProfileD.d) { RedrawProfileW( screenProfileD.d, NULL, 0, 0 ); } } #include "bitmaps/profile.xpm3" EXPORT void InitCmdProfile(wMenu_p menu) { log_profile = LogFindIndex("profile"); ParamRegister(&profilePG); AddMenuButton(menu, CmdProfile, "cmdProfile", _("Profile"), wIconCreatePixMap(profile_xpm3[iconSize]), LEVEL0_50, IC_LCLICK|IC_CMDMENU|IC_POPUP3, ACCL_PROFILE, NULL); profilePopupM = MenuRegister("Profile Mode"); profilePopupToggles[0] = wMenuToggleCreate(profilePopupM, "", _("Define"), 0, FALSE, ProfileSubCommand, I2VP(0)); profilePopupToggles[1] = wMenuToggleCreate(profilePopupM, "", _("Ignore"), 0, FALSE, ProfileSubCommand, I2VP(1)); profilePopupToggles[2] = wMenuToggleCreate(profilePopupM, "", _("None"), 0, FALSE, ProfileSubCommand, I2VP(2)); RegisterChangeNotification(ProfileChange); }