/** \file cselect.c * Handle selecting / unselecting track and basic operations on the selection */ /* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "draw.h" #include "ccurve.h" #include "tcornu.h" #include "tbezier.h" #include "track.h" #define PRIVATE_EXTRADATA #include "compound.h" #include "cselect.h" #include "cundo.h" #include "custom.h" #include "fileio.h" #include "i18n.h" #include "layout.h" #include "messages.h" #include "param.h" #include "track.h" #include "utility.h" #include "draw.h" #include "misc.h" #include "trackx.h" #include "bitmaps/bmendpt.xbm" #include "bitmaps/bma0.xbm" #include "bitmaps/bma45.xbm" #include "bitmaps/bma90.xbm" #include "bitmaps/bma135.xbm" #define SETMOVEMODE "MOVEMODE" EXPORT wIndex_t selectCmdInx; EXPORT wIndex_t moveCmdInx; EXPORT wIndex_t rotateCmdInx; #define MAXMOVEMODE (3) static long moveMode = MAXMOVEMODE; static BOOL_T enableMoveDraw = TRUE; static BOOL_T move0B; struct extraData { char junk[2000]; }; static wDrawBitMap_p endpt_bm; static wDrawBitMap_p angle_bm[4]; track_p IsInsideABox(coOrd pos); static track_p moveDescTrk; static coOrd moveDescPos; int incrementalDrawLimit = 20; static int microCount = 0; static dynArr_t tlist_da; #define Tlist(N) DYNARR_N( track_p, tlist_da, N ) #define TlistAppend( T ) \ { DYNARR_APPEND( track_p, tlist_da, 10 );\ Tlist(tlist_da.cnt-1) = T; } BOOL_T TListSearch(track_p T) { for (int i=0;i incrementalDrawLimit) { doRedraw = TRUE; } else { wDrawDelayUpdate( mainD.d, TRUE ); } selectedTrackCount = 0; trk = NULL; while ( TrackIterate( &trk ) ) { if ((!select) || GetLayerVisible( GetTrkLayer( trk ))) { if (select) selectedTrackCount++; if ((GetTrkSelected(trk)!=0) != select) { if (select) SetTrkBits( trk, TB_SELECTED ); else ClrTrkBits( trk, TB_SELECTED ); if (!doRedraw) SetTrkBits( trk, TB_SELREDRAW ); DrawTrackAndEndPts( trk, wDrawColorBlack ); } } } SelectedTrackCountChange(); if (doRedraw) { MainRedraw(); // SetAllTrackSelect } else { RedrawSelectedTracksBoundary(); wDrawDelayUpdate( mainD.d, FALSE ); } } /* Invert selected state of all visible non-module objects. * * \param none * \return none */ EXPORT void InvertTrackSelect( void *ptr ) { track_p trk; trk = NULL; while ( TrackIterate( &trk ) ) { if (GetLayerVisible( GetTrkLayer( trk )) && !GetLayerModule(GetTrkLayer( trk ))) { SelectOneTrack( trk, GetTrkSelected(trk)==0 ); } } RedrawSelectedTracksBoundary(); SelectedTrackCountChange(); MainRedraw(); // InvertTrackSelect } /* Select orphaned (ie single) track pieces. * * \param none * \return none */ EXPORT void OrphanedTrackSelect( void *ptr ) { track_p trk; EPINX_T ep; int cnt ; trk = NULL; while( TrackIterate( &trk ) ) { cnt = 0; if( GetLayerVisible( GetTrkLayer( trk ) && !GetLayerModule(GetTrkLayer(trk)))) { for( ep = 0; ep < GetTrkEndPtCnt( trk ); ep++ ) { if( GetTrkEndTrk( trk, ep ) ) cnt++; } if( !cnt && GetTrkEndPtCnt( trk )) { SetTrkBits( trk, TB_SELECTED ); DrawTrackAndEndPts( trk, wDrawColorBlack ); selectedTrackCount++; } } } RedrawSelectedTracksBoundary(); SelectedTrackCountChange(); MainRedraw(); // OrphanTrackSelect } static void SelectOneTrack( track_p trk, wBool_t selected ) { BOOL_T bRedraw = (GetTrkSelected(trk) != 0) != selected; if ( !bRedraw ) { ClrTrkBits( trk, TB_SELREDRAW ); return; } SetTrkBits( trk, TB_SELREDRAW ); if (selected) { SetTrkBits( trk, TB_SELECTED ); selectedTrackCount++; } else { ClrTrkBits( trk, TB_SELECTED ); selectedTrackCount--; } SelectedTrackCountChange(); DrawTrackAndEndPts( trk, wDrawColorBlack ); } static void HighlightSelectedTracks( track_p trk_ignore, BOOL_T box, BOOL_T invert ) { track_p trk = NULL; if ( selectedTrackCount == 0 ) return; while ( TrackIterate( &trk ) ) { if (trk == trk_ignore) continue; if(GetTrkSelected(trk)) { if (!GetLayerVisible( GetTrkLayer( trk ))) continue; if (invert) DrawTrack(trk,&tempD,wDrawColorPreviewUnselected); else DrawTrack(trk,&tempD,wDrawColorPreviewSelected ); } } } static void SelectConnectedTracks( track_p trk, BOOL_T display_only ) { track_p trk1; int inx; EPINX_T ep; tlist_da.cnt = 0; TlistAppend( trk ); InfoCount( 0 ); if (!display_only) wDrawDelayUpdate( mainD.d, FALSE ); for (inx=0; inx 0 && (selectedTrackCount == 0) && !display_only ) return; trk = Tlist(inx); if (inx!=0 && GetTrkSelected(trk)) { if (display_only) DrawTrack(trk,&tempD,wDrawColorPreviewSelected ); continue; } else if (GetTrkSelected(trk)) { if (display_only) DrawTrack(trk,&tempD,wDrawColorPreviewUnselected); continue; } for (ep=0; ep0) { UndoStart( _("Delete Tracks"), "delete" ); wDrawDelayUpdate( mainD.d, TRUE ); wDrawDelayUpdate( mapD.d, TRUE ); DoSelectedTracks( DeleteTrack ); DoRedraw(); // SelectDelete wDrawDelayUpdate( mainD.d, FALSE ); wDrawDelayUpdate( mapD.d, FALSE ); selectedTrackCount = 0; SelectedTrackCountChange(); UndoEnd(); } else { ErrorMessage( MSG_NO_SELECTED_TRK ); } } BOOL_T flipHiddenDoSelectRecount; static BOOL_T FlipHidden( track_p trk, BOOL_T junk ) { EPINX_T i; track_p trk2; DrawTrackAndEndPts( trk, wDrawColorWhite ); /*UndrawNewTrack( trk ); for (i=0; i0) { flipHiddenDoSelectRecount = FALSE; UndoStart( _("Hide Tracks (Tunnel)"), "tunnel" ); wDrawDelayUpdate( mainD.d, TRUE ); DoSelectedTracks( FlipHidden ); wDrawDelayUpdate( mainD.d, FALSE ); UndoEnd(); } else { ErrorMessage( MSG_NO_SELECTED_TRK ); } if ( flipHiddenDoSelectRecount ) SelectRecount(); } EXPORT void SelectBridge( void ) { if (SelectedTracksAreFrozen()) return; if (selectedTrackCount>0) { flipHiddenDoSelectRecount = FALSE; UndoStart( _("Bridge Tracks "), "bridge" ); wDrawDelayUpdate( mainD.d, TRUE ); DoSelectedTracks( FlipBridge ); wDrawDelayUpdate( mainD.d, FALSE ); UndoEnd(); } else { ErrorMessage( MSG_NO_SELECTED_TRK ); } MainRedraw(); // SelectBridge } EXPORT void SelectTies( void ) { if (SelectedTracksAreFrozen()) return; if (selectedTrackCount>0) { flipHiddenDoSelectRecount = FALSE; UndoStart( _("Ties Tracks "), "noties" ); wDrawDelayUpdate( mainD.d, TRUE ); DoSelectedTracks( FlipTies ); wDrawDelayUpdate( mainD.d, FALSE ); UndoEnd(); } else { ErrorMessage( MSG_NO_SELECTED_TRK ); } MainRedraw(); // SelectTies } void SelectRecount( void ) { track_p trk; selectedTrackCount = 0; trk = NULL; while ( TrackIterate( &trk ) ) { if (GetTrkSelected(trk)) { selectedTrackCount++; } } SelectedTrackCountChange(); } static BOOL_T SetLayer( track_p trk, BOOL_T junk ) { UndoModify( trk ); SetTrkLayer( trk, curLayer ); return TRUE; } EXPORT void MoveSelectedTracksToCurrentLayer( void ) { if (SelectedTracksAreFrozen()) return; if (selectedTrackCount>0) { UndoStart( _("Move To Current Layer"), "changeLayer" ); DoSelectedTracks( SetLayer ); UndoEnd(); } else { ErrorMessage( MSG_NO_SELECTED_TRK ); } } EXPORT void SelectCurrentLayer( void ) { track_p trk; trk = NULL; while ( TrackIterate( &trk ) ) { if ((!GetTrkSelected(trk)) && GetTrkLayer(trk) == curLayer ) { SelectOneTrack( trk, TRUE ); } } RedrawSelectedTracksBoundary(); } static BOOL_T ClearElevation( track_p trk, BOOL_T junk ) { EPINX_T ep; for ( ep=0; ep0) { UndoStart( _("Clear Elevations"), "clear elevations" ); DoSelectedTracks( ClearElevation ); UpdateAllElevations(); UndoEnd(); } else { ErrorMessage( MSG_NO_SELECTED_TRK ); } } static DIST_T elevDelta; static BOOL_T AddElevation( track_p trk, BOOL_T junk ) { track_p trk1; EPINX_T ep, ep1; int mode; DIST_T elev; for ( ep=0; ep= 0) { if (GetTrkSelected(trk1) && GetTrkIndex(trk1)0) { elevDelta = delta; UndoStart( _("Add Elevations"), "add elevations" ); DoSelectedTracks( AddElevation ); UndoEnd(); } else { ErrorMessage( MSG_NO_SELECTED_TRK ); } UpdateAllElevations(); } EXPORT void DoRefreshCompound( void ) { if (SelectedTracksAreFrozen()) return; if (selectedTrackCount>0) { UndoStart( _("Refresh Compound"), "refresh compound" ); DoSelectedTracks( RefreshCompound ); RefreshCompound( NULL, FALSE ); UndoEnd(); MainRedraw(); // DoRefreshCompound } else { ErrorMessage( MSG_NO_SELECTED_TRK ); } } static drawCmd_t tempSegsD = { NULL, &tempSegDrawFuncs, 0, 1, 0.0, {0.0, 0.0}, {0.0, 0.0}, Pix2CoOrd, CoOrd2Pix }; EXPORT void WriteSelectedTracksToTempSegs( void ) { track_p trk; DYNARR_RESET( trkSeg_t, tempSegs_da ); tempSegsD.dpi = mainD.dpi; for ( trk=NULL; TrackIterate(&trk); ) { if ( GetTrkSelected( trk ) ) { if ( IsTrack( trk ) ) continue; ClrTrkBits( trk, TB_SELECTED ); DrawTrack( trk, &tempSegsD, wDrawColorBlack ); SetTrkBits( trk, TB_SELECTED ); } } } static char rescaleFromScale[20]; static char rescaleFromGauge[20]; static char * rescaleToggleLabels[] = { N_("Scale"), N_("Ratio"), NULL }; static long rescaleMode; static wIndex_t rescaleFromScaleInx; static wIndex_t rescaleFromGaugeInx; static wIndex_t rescaleToScaleInx; static wIndex_t rescaleToGaugeInx; static wIndex_t rescaleToInx; static long rescaleNoChangeDim = FALSE; static FLOAT_T rescalePercent; static char * rescaleChangeDimLabels[] = { N_("Do not resize track"), NULL }; static paramFloatRange_t r0o001_10000 = { 0.001, 10000.0 }; static paramData_t rescalePLs[] = { #define I_RESCALE_MODE (0) { PD_RADIO, &rescaleMode, "toggle", PDO_NOPREF, &rescaleToggleLabels, N_("Rescale by:"), BC_HORZ|BC_NOBORDER }, #define I_RESCALE_FROM_SCALE (1) { PD_STRING, rescaleFromScale, "fromS", PDO_NOPREF|PDO_STRINGLIMITLENGTH, (void*)100, N_("From:"),0, 0, sizeof(rescaleFromScale)}, #define I_RESCALE_FROM_GAUGE (2) { PD_STRING, rescaleFromGauge, "fromG", PDO_NOPREF|PDO_DLGHORZ | PDO_STRINGLIMITLENGTH, (void*)100, " / ", 0, 0, sizeof(rescaleFromGauge)}, #define I_RESCALE_TO_SCALE (3) { PD_DROPLIST, &rescaleToScaleInx, "toS", PDO_NOPREF|PDO_LISTINDEX, (void *)100, N_("To: ") }, #define I_RESCALE_TO_GAUGE (4) { PD_DROPLIST, &rescaleToGaugeInx, "toG", PDO_NOPREF|PDO_LISTINDEX|PDO_DLGHORZ, NULL, " / " }, #define I_RESCALE_CHANGE (5) { PD_TOGGLE, &rescaleNoChangeDim, "change-dim", 0, &rescaleChangeDimLabels, "", BC_HORZ|BC_NOBORDER }, #define I_RESCALE_PERCENT (6) { PD_FLOAT, &rescalePercent, "ratio", 0, &r0o001_10000, N_("Ratio") }, { PD_MESSAGE, "%", NULL, PDO_DLGHORZ } }; static paramGroup_t rescalePG = { "rescale", 0, rescalePLs, sizeof rescalePLs/sizeof rescalePLs[0] }; static long getboundsCount; static coOrd getboundsLo, getboundsHi; static BOOL_T GetboundsDoIt( track_p trk, BOOL_T junk ) { coOrd hi, lo; GetBoundingBox( trk, &hi, &lo ); if ( getboundsCount == 0 ) { getboundsLo = lo; getboundsHi = hi; } else { if ( lo.x < getboundsLo.x ) getboundsLo.x = lo.x; if ( lo.y < getboundsLo.y ) getboundsLo.y = lo.y; if ( hi.x > getboundsHi.x ) getboundsHi.x = hi.x; if ( hi.y > getboundsHi.y ) getboundsHi.y = hi.y; } getboundsCount++; return TRUE; } static coOrd rescaleShift; static BOOL_T RescaleDoIt( track_p trk, BOOL_T junk ) { EPINX_T ep, ep1; track_p trk1; UndrawNewTrack( trk ); UndoModify(trk); if ( rescalePercent != 100.0 ) { for (ep=0; ep mapD.size.x ) { d = getboundsHi.x - mapD.size.x; if ( getboundsLo.x < d ) d = getboundsLo.x; getboundsHi.x -= d; getboundsLo.x -= d; } if ( getboundsLo.y < 0 ) { getboundsHi.y -= getboundsLo.y; getboundsLo.y = 0; } else if ( getboundsHi.y > mapD.size.y ) { d = getboundsHi.y - mapD.size.y; if ( getboundsLo.y < d ) d = getboundsLo.y; getboundsHi.y -= d; getboundsLo.y -= d; } if ( rescaleNoChangeDim == 0 && (getboundsHi.x > mapD.size.x || getboundsHi.y > mapD.size.y )) { NoticeMessage( MSG_RESCALE_TOO_BIG, _("Ok"), NULL, FormatDistance(getboundsHi.x), FormatDistance(getboundsHi.y) ); } rescaleShift.x = (getboundsLo.x+getboundsHi.x)/2.0 - center.x*ratio; rescaleShift.y = (getboundsLo.y+getboundsHi.y)/2.0 - center.y*ratio; rescaleToInx = GetScaleInx( rescaleToScaleInx, rescaleToGaugeInx ); DoSelectedTracks( RescaleDoIt ); wHide( rescalePG.win ); } static void RescaleDlgUpdate( paramGroup_p pg, int inx, void * valueP ) { switch (inx) { case I_RESCALE_MODE: wControlShow( pg->paramPtr[I_RESCALE_FROM_SCALE].control, rescaleMode==0 ); wControlActive( pg->paramPtr[I_RESCALE_FROM_SCALE].control, FALSE ); wControlShow( pg->paramPtr[I_RESCALE_TO_SCALE].control, rescaleMode==0 ); wControlShow( pg->paramPtr[I_RESCALE_FROM_GAUGE].control, rescaleMode==0 ); wControlActive( pg->paramPtr[I_RESCALE_FROM_GAUGE].control, FALSE ); wControlShow( pg->paramPtr[I_RESCALE_TO_GAUGE].control, rescaleMode==0 ); wControlShow( pg->paramPtr[I_RESCALE_CHANGE].control, rescaleMode==0 ); wControlActive( pg->paramPtr[I_RESCALE_PERCENT].control, rescaleMode==1 ); if ( rescaleMode!=0 ) break; case I_RESCALE_TO_SCALE: LoadGaugeList( (wList_p)rescalePLs[I_RESCALE_TO_GAUGE].control, *((int *)valueP) ); rescaleToGaugeInx = 0; ParamLoadControl( pg, I_RESCALE_TO_GAUGE ); ParamLoadControl( pg, I_RESCALE_TO_SCALE ); rescalePercent = GetScaleDescRatio(rescaleFromScaleInx)/GetScaleDescRatio(rescaleToScaleInx)*100.0; wControlActive( pg->paramPtr[I_RESCALE_CHANGE].control, (rescaleFromScaleInx != rescaleToScaleInx) ); ParamLoadControl( pg, I_RESCALE_PERCENT ); break; case I_RESCALE_TO_GAUGE: ParamLoadControl( pg, I_RESCALE_TO_GAUGE ); break; case I_RESCALE_FROM_SCALE: ParamLoadControl( pg, I_RESCALE_FROM_SCALE ); break; case I_RESCALE_FROM_GAUGE: ParamLoadControl( pg, I_RESCALE_FROM_GAUGE ); break; case I_RESCALE_CHANGE: ParamLoadControl( pg, I_RESCALE_CHANGE ); break; case -1: break; } ParamDialogOkActive( pg, rescalePercent!=100.0 || rescaleFromGaugeInx != rescaleToGaugeInx ); } /** * Get the scale gauge information for the selected track pieces. * FIXME: special cases like tracks pieces with different gauges or scale need to be handled * * \param IN trk track element * \param IN junk * \return TRUE; */ static BOOL_T SelectedScaleGauge( track_p trk, BOOL_T junk ) { char *scaleName; SCALEINX_T scale; SCALEDESCINX_T scaleInx; GAUGEINX_T gaugeInx; scale = GetTrkScale( trk ); scaleName = GetScaleName( scale ); if( strcmp( scaleName, "*" )) { GetScaleGauge( scale, &scaleInx, &gaugeInx ); strcpy( rescaleFromScale,GetScaleDesc( scaleInx )); strcpy( rescaleFromGauge, GetGaugeDesc( scaleInx, gaugeInx )); rescaleFromScaleInx = scaleInx; rescaleFromGaugeInx = gaugeInx; rescaleToScaleInx = scaleInx; rescaleToGaugeInx = gaugeInx; } return TRUE; } /** * Bring up the rescale dialog. The dialog for rescaling the selected pieces * of track is created if necessary and shown. Handling of user input is done via * RescaleDlgUpdate() */ EXPORT void DoRescale( void ) { if ( rescalePG.win == NULL ) { ParamCreateDialog( &rescalePG, MakeWindowTitle(_("Rescale")), _("Ok"), RescaleDlgOk, wHide, TRUE, NULL, F_BLOCK, RescaleDlgUpdate ); LoadScaleList( (wList_p)rescalePLs[I_RESCALE_TO_SCALE].control ); LoadGaugeList( (wList_p)rescalePLs[I_RESCALE_TO_GAUGE].control, GetLayoutCurScaleDesc() ); /* set correct gauge list here */ rescaleFromScaleInx = GetLayoutCurScale(); rescaleToScaleInx = rescaleFromScaleInx; rescalePercent = 100.0; } DoSelectedTracks( SelectedScaleGauge ); RescaleDlgUpdate( &rescalePG, I_RESCALE_MODE, &rescaleMode ); RescaleDlgUpdate( &rescalePG, I_RESCALE_CHANGE, &rescaleMode ); RescaleDlgUpdate( &rescalePG, I_RESCALE_FROM_GAUGE, rescaleFromGauge ); RescaleDlgUpdate( &rescalePG, I_RESCALE_FROM_SCALE, rescaleFromScale ); RescaleDlgUpdate( &rescalePG, I_RESCALE_TO_SCALE, &rescaleToScaleInx ); RescaleDlgUpdate( &rescalePG, I_RESCALE_TO_GAUGE, &rescaleToGaugeInx ); wShow( rescalePG.win ); } static void DrawSelectedTracksD( drawCmd_p d, wDrawColor color ) { wIndex_t inx; track_p trk; coOrd lo, hi; /*wDrawDelayUpdate( d->d, TRUE );*/ for (inx=0; inxorig, d->size, lo, hi ) ) continue; } if (color != wDrawColorWhite) ClrTrkBits(trk, TB_UNDRAWN); DrawTrack( trk, d, color ); if (color == wDrawColorWhite) SetTrkBits( trk, TB_UNDRAWN ); } /*wDrawDelayUpdate( d->d, FALSE );*/ } static BOOL_T AddSelectedTrack( track_p trk, BOOL_T junk ) { DYNARR_APPEND( track_p, tlist_da, 10 ); DYNARR_LAST( track_p, tlist_da ) = trk; return TRUE; } static coOrd moveOrig; static ANGLE_T moveAngle; static coOrd moveD_hi, moveD_lo; static drawCmd_t moveD = { NULL, &tempSegDrawFuncs, DC_SIMPLE, 1, 0.0, {0.0, 0.0}, {0.0, 0.0}, Pix2CoOrd, CoOrd2Pix }; /* Draw selected (on-screen) tracks to tempSegs, and use drawSegs to draw them (moved/rotated) to mainD Incremently add new tracks as they scroll on-screen. */ static int movedCnt; static void AccumulateTracks( void ) { wIndex_t inx; track_p trk; coOrd lo, hi; /*wDrawDelayUpdate( moveD.d, TRUE );*/ movedCnt =0; for ( inx = 0; inx= moveD_lo.x && lo.y <= moveD_hi.y && hi.y >= moveD_lo.y ) { if (!QueryTrack(trk,Q_IS_CORNU)) DrawTrack( trk, &moveD, wDrawColorBlack ); } movedCnt++; } } InfoCount( movedCnt ); /*wDrawDelayUpdate( moveD.d, FALSE );*/ } static void AddEndCornus() { for (int i=0;i=0;j--) { tc = GetTrkEndTrk(trk,j); if (tc && !GetTrkSelected(tc) && QueryTrack(tc,Q_IS_CORNU) && !QueryTrack(trk,Q_IS_CORNU)) { //On end and cornu SelectOneTrack( tc, TRUE ); DYNARR_APPEND(track_p,tlist_da,1); //Add to selected list DYNARR_LAST(track_p,tlist_da) = tc; } } } } static void GetMovedTracks( BOOL_T undraw ) { wSetCursor( mainD.d, wCursorWait ); DYNARR_RESET( track_p, tlist_da ); DoSelectedTracks( AddSelectedTrack ); AddEndCornus(); //Include Cornus that are attached at ends of selected DYNARR_RESET( trkSeg_p, tempSegs_da ); moveOrig = mainD.orig; movedCnt = 0; InfoCount(0); wSetCursor( mainD.d, defaultCursor ); moveD_hi = moveD_lo = mainD.orig; moveD_hi.x += mainD.size.x; moveD_hi.y += mainD.size.y; AccumulateTracks(); if (undraw) { DrawSelectedTracksD( &mainD, wDrawColorWhite ); } } static void SetMoveD( BOOL_T moveB, coOrd orig, ANGLE_T angle ) { int inx; moveOrig.x = orig.x; moveOrig.y = orig.y; moveAngle = angle; if (!moveB) { Rotate( &orig, zero, angle ); moveOrig.x -= orig.x; moveOrig.y -= orig.y; } if (moveB) { moveD_lo.x = mainD.orig.x - orig.x; moveD_lo.y = mainD.orig.y - orig.y; moveD_hi = moveD_lo; moveD_hi.x += mainD.size.x; moveD_hi.y += mainD.size.y; } else { coOrd corner[3]; corner[2].x = mainD.orig.x; corner[0].x = corner[1].x = mainD.orig.x + mainD.size.x; corner[0].y = mainD.orig.y; corner[1].y = corner[2].y = mainD.orig.y + mainD.size.y; moveD_hi = mainD.orig; Rotate( &moveD_hi, orig, -angle ); moveD_lo = moveD_hi; for (inx=0;inx<3;inx++) { Rotate( &corner[inx], orig, -angle ); if (corner[inx].x < moveD_lo.x) moveD_lo.x = corner[inx].x; if (corner[inx].y < moveD_lo.y) moveD_lo.y = corner[inx].y; if (corner[inx].x > moveD_hi.x) moveD_hi.x = corner[inx].x; if (corner[inx].y > moveD_hi.y) moveD_hi.y = corner[inx].y; } } AccumulateTracks(); } static void DrawMovedTracks( void ) { int inx; track_p trk; dynArr_t cornu_segs; DrawSegs( &tempD, moveOrig, moveAngle, &tempSegs(0), tempSegs_da.cnt, 0.0, wDrawColorBlack ); for ( inx=0; inx=0) { DIST_T d = FindDistance(pos1,GetTrkEndPos(tt,epp)); if (IsClose(d)) { *ep1 = epp; *ep2 = i; *t1 = tt; *t2 = ts; return TRUE; } } else { epp = PickEndPoint(pos2,tt); //Any close end point (even joined) if (epp>=0) { ct = GetTrkEndTrk(tt,epp); if (ct && GetTrkSelected(ct)) { //Point is junction to selected track - so will be broken DIST_T d = FindDistance(pos1,GetTrkEndPos(tt,epp)); if (IsClose(d)) { *ep1 = epp; *ep2 = i; *t1 = tt; *t2 = ts; return TRUE; } } } } } } } } return FALSE; } void DrawHighlightLayer(int layer) { track_p ts = NULL; BOOL_T initial = TRUE; coOrd layer_hi = zero,layer_lo = zero; while ( TrackIterate( &ts ) ) { if ( !GetLayerVisible( GetTrkLayer( ts))) continue; if (!GetTrkSelected(ts)) continue; if (GetTrkLayer(ts) != layer) continue; coOrd hi,lo; GetBoundingBox(ts, &hi, &lo); if (initial) { layer_hi = hi; layer_lo = lo; initial = FALSE; } else { if (layer_hi.x < hi.x ) layer_hi.x = hi.x; if (layer_hi.y < hi.y ) layer_hi.y = hi.y; if (layer_lo.x > lo.x ) layer_lo.x = lo.x; if (layer_lo.y > lo.y ) layer_lo.y = lo.y; } } wPos_t margin = (wPos_t)(10.5*mainD.scale/mainD.dpi); layer_hi.x +=margin; layer_hi.y +=margin; layer_lo.x -=margin; layer_lo.y -=margin; wPos_t rect[4][2]; int type[4]; coOrd top_left, bot_right; top_left.x = layer_lo.x; top_left.y = layer_hi.y; bot_right.x = layer_hi.x; bot_right.y = layer_lo.y; type[0] = type[1] = type[2] = type[3] = 0; mainD.CoOrd2Pix(&mainD,layer_lo,&rect[0][0],&rect[0][1]); mainD.CoOrd2Pix(&mainD,top_left,&rect[1][0],&rect[1][1]); mainD.CoOrd2Pix(&mainD,layer_hi,&rect[2][0],&rect[2][1]); mainD.CoOrd2Pix(&mainD,bot_right,&rect[3][0],&rect[3][1]); wDrawPolygon(tempD.d,rect,(wPolyLine_e *)type,4,wDrawColorPowderedBlue,0,wDrawLineDash,wDrawOptTemp,0,0); } void SetUpMenu2(coOrd pos, track_p trk) { wMenuPushEnable( menuPushModify,FALSE); wMenuPushEnable(descriptionMI,FALSE); wMenuPushEnable( rotateAlignMI, FALSE ); wMenuPushEnable( hideMI, FALSE ); wMenuPushEnable( bridgeMI, FALSE ); wMenuPushEnable( tiesMI, FALSE ); if ((trk) && QueryTrack(trk,Q_CAN_ADD_ENDPOINTS)) { //Turntable snap to center if within 1/4 radius trackParams_t trackParams; if (GetTrackParams(PARAMS_CORNU, trk, pos, &trackParams)) { DIST_T dist = FindDistance(pos, trackParams.ttcenter); if (dist < trackParams.ttradius/4) { cmdMenuPos = trackParams.ttcenter; } } } if (trk && !QueryTrack( trk, Q_IS_DRAW )) { wMenuPushEnable( hideMI, TRUE ); wMenuPushEnable( bridgeMI, TRUE ); wMenuPushEnable( tiesMI, TRUE ); } if (trk) { wMenuPushEnable( menuPushModify, (QueryTrack( trk, Q_CAN_MODIFY_CONTROL_POINTS ) || QueryTrack( trk, Q_IS_CORNU ) || (QueryTrack( trk, Q_IS_DRAW ) && !QueryTrack( trk, Q_IS_TEXT )) || QueryTrack( trk, Q_IS_ACTIVATEABLE))); } if ((trk)) { wMenuPushEnable(descriptionMI, QueryTrack( trk, Q_HAS_DESC )); moveDescTrk = trk; moveDescPos = pos; } if (selectedTrackCount>0) wMenuPushEnable( rotateAlignMI, TRUE ); } static STATUS_T CmdMove( wAction_t action, coOrd pos ) { static coOrd base; static coOrd orig; static int state; static EPINX_T ep1; static EPINX_T ep2; static track_p t1; static track_p t2; static BOOL_T doingMove; switch( action & 0xFF) { case C_START: DYNARR_RESET(trkSeg_t,anchors_da); if (selectedTrackCount == 0) { ErrorMessage( MSG_NO_SELECTED_TRK ); return C_TERMINATE; } if (SelectedTracksAreFrozen()) { return C_TERMINATE; } InfoMessage( _("Drag to move selected tracks - Shift+Ctrl+Arrow micro-steps the move") ); state = 0; ep1 = -1; ep2 = -1; doingMove = FALSE; break; case wActionMove: DYNARR_RESET(trkSeg_t,anchors_da); CreateMoveAnchor(pos); break; case C_DOWN: DYNARR_RESET(trkSeg_t,anchors_da); if (doingMove) { doingMove = FALSE; UndoEnd(); } if (SelectedTracksAreFrozen()) { return C_TERMINATE; } UndoStart( _("Move Tracks"), "move" ); base = zero; orig = pos; GetMovedTracks(TRUE); SetMoveD( TRUE, base, 0.0 ); drawCount = 0; state = 1; return C_CONTINUE; case C_MOVE: DYNARR_RESET(trkSeg_t,anchors_da); ep1=-1; ep2=-1; drawEnable = enableMoveDraw; base.x = pos.x - orig.x; base.y = pos.y - orig.y; SnapPos( &base ); SetMoveD( TRUE, base, 0.0 ); if (((MyGetKeyState()&(WKEY_ALT)) == 0) == magneticSnap) { // ALT if (FindEndIntersection(base,zero,0.0,&t1,&ep1,&t2,&ep2)) { coOrd pos2 = GetTrkEndPos(t2,ep2); pos2.x +=base.x; pos2.y +=base.y; CreateEndAnchor(pos2,FALSE); CreateEndAnchor(GetTrkEndPos(t1,ep1),TRUE); } } #ifdef DRAWCOUNT InfoMessage( " [%s %s] #%ld", FormatDistance(base.x), FormatDistance(base.y), drawCount ); #else InfoMessage( " [%s %s]", FormatDistance(base.x), FormatDistance(base.y) ); #endif drawEnable = TRUE; return C_CONTINUE; case C_UP: DYNARR_RESET(trkSeg_t,anchors_da); state = 0; FreeTempStrings(); if (t1 && ep1>=0 && t2 && ep2>=0) { MoveToJoin(t2,ep2,t1,ep1); } else { MoveTracks( FALSE, TRUE, FALSE, base, zero, 0.0, TRUE ); } ep1 = -1; ep2 = -1; tlist_da.cnt = 0; return C_TERMINATE; case C_CMDMENU: if (doingMove) UndoEnd(); doingMove = FALSE; base = pos; track_p trk = OnTrack(&pos, FALSE, FALSE); //Note pollutes pos if turntable if ((trk) && QueryTrack(trk,Q_CAN_ADD_ENDPOINTS)) { //Turntable snap to center if within 1/4 radius trackParams_t trackParams; if (GetTrackParams(PARAMS_CORNU, trk, pos, &trackParams)) { DIST_T dist = FindDistance(base, trackParams.ttcenter); if (dist < trackParams.ttradius/4) { cmdMenuPos = trackParams.ttcenter; } } } moveDescPos = pos; moveDescTrk = trk; SetUpMenu2(pos,trk); menuPos = pos; wMenuPopupShow( selectPopup2M ); return C_CONTINUE; case C_TEXT: if ((action>>8) == 'c') { panCenter = pos; LOG( log_pan, 2, ( "PanCenter:Sel-%d %0.3f %0.3f\n", __LINE__, panCenter.x, panCenter.y ) ); PanHere((void*)0); } if ((action>>8) == 'e') { DoZoomExtents(0); } if ((action>>8) == '0' || (action>>8 == 'o')) { PanMenuEnter('o'); } break; case C_REDRAW: /* DO_REDRAW */ if (anchors_da.cnt) DrawSegs( &tempD, zero, 0.0, &anchors(0), anchors_da.cnt, trackGauge, wDrawColorBlack ); if ( state == 0 ) break; DrawMovedTracks(); break; case wActionExtKey: if (state) return C_CONTINUE; if (SelectedTracksAreFrozen()) return C_TERMINATE; if ((MyGetKeyState() & (WKEY_SHIFT | WKEY_CTRL)) == (WKEY_SHIFT | WKEY_CTRL)) { //Both base = zero; DIST_T w = tempD.scale/tempD.dpi; switch((wAccelKey_e) action>>8) { case wAccelKey_Up: base.y = w; break; case wAccelKey_Down: base.y = -w; break; case wAccelKey_Left: base.x = -w; break; case wAccelKey_Right: base.x = w; break; default: return C_CONTINUE; break; } drawEnable = enableMoveDraw; GetMovedTracks(TRUE); if (!doingMove) UndoStart( _("Move Tracks"), "move" ); doingMove = TRUE; SetMoveD( TRUE, base, 0.0 ); MoveTracks( FALSE, TRUE, FALSE, base, zero, 0.0, FALSE ); ++microCount; if (microCount>5) { microCount = 0; MainRedraw(); // Micro step move } return C_CONTINUE; } break; case C_FINISH: if (doingMove) { doingMove = FALSE; UndoEnd(); } tlist_da.cnt = 0; break; case C_CONFIRM: case C_CANCEL: if (doingMove) { doingMove = FALSE; UndoUndo(); } tlist_da.cnt = 0; break; default: break; } return C_CONTINUE; } static int rotateAlignState = 0; static void RotateAlign( BOOL_T align ) { rotateAlignState = 0; if (align) { rotateAlignState = 1; doingAlign = TRUE; mode = MOVE; InfoMessage( _("Align: Click on a selected object to be aligned") ); } } static STATUS_T CmdRotate( wAction_t action, coOrd pos ) { static coOrd base; static coOrd orig_base; static coOrd orig; static ANGLE_T angle; static BOOL_T drawnAngle; static ANGLE_T baseAngle; static BOOL_T clockwise; static BOOL_T direction_set; static track_p trk; ANGLE_T angle1; coOrd pos1; static int state; static EPINX_T ep1; static EPINX_T ep2; static track_p t1; static track_p t2; switch( action ) { case C_START: DYNARR_RESET(trkSeg_t,anchors_da); state = 0; if (selectedTrackCount == 0) { ErrorMessage( MSG_NO_SELECTED_TRK ); return C_TERMINATE; } if (SelectedTracksAreFrozen()) { return C_TERMINATE; } InfoMessage( _("Drag to rotate selected tracks, Shift+RightClick for QuickRotate Menu") ); wMenuPushEnable( rotateAlignMI, TRUE ); rotateAlignState = 0; ep1 = -1; ep2 = -1; break; case wActionMove: DYNARR_RESET(trkSeg_t,anchors_da); CreateRotateAnchor(pos); break; case C_DOWN: DYNARR_RESET(trkSeg_t,anchors_da); state = 1; if (SelectedTracksAreFrozen()) { return C_TERMINATE; } UndoStart( _("Rotate Tracks"), "rotate" ); if ( rotateAlignState == 0 ) { drawnAngle = FALSE; angle = 0.0; base = orig = pos; trk = OnTrack(&pos, FALSE, FALSE); //Note pollutes pos if turntable if ((trk) && QueryTrack(trk,Q_CAN_ADD_ENDPOINTS)) { //Turntable snap to center if within 1/4 radius trackParams_t trackParams; if (GetTrackParams(PARAMS_CORNU, trk, pos, &trackParams)) { DIST_T dist = FindDistance(base, trackParams.ttcenter); if (dist < trackParams.ttradius/4) { base = orig = trackParams.ttcenter; InfoMessage( _("Center of Rotation snapped to Turntable center") ); } } } CreateRotateAnchor(orig); GetMovedTracks(TRUE); SetMoveD( FALSE, base, angle ); /*DrawLine( &mainD, base, orig, 0, wDrawColorBlack ); DrawMovedTracks(FALSE, orig, angle);*/ } else { pos1 = pos; drawnAngle = FALSE; onTrackInSplit = TRUE; trk = OnTrack( &pos, TRUE, FALSE ); onTrackInSplit = FALSE; if ( trk == NULL ) return C_CONTINUE; angle1 = NormalizeAngle( GetAngleAtPoint( trk, pos, NULL, NULL ) ); if ( rotateAlignState == 1 ) { if ( !GetTrkSelected(trk) ) { NoticeMessage( MSG_1ST_TRACK_MUST_BE_SELECTED, _("Ok"), NULL ); } else { base = pos; baseAngle = angle1; getboundsCount = 0; DoSelectedTracks( GetboundsDoIt ); orig.x = (getboundsLo.x+getboundsHi.x)/2.0; orig.y = (getboundsLo.y+getboundsHi.y)/2.0; /*printf( "orig = [%0.3f %0.3f], baseAngle = %0.3f\n", orig.x, orig.y, baseAngle );*/ } } else { if ( GetTrkSelected(trk) ) { ErrorMessage( MSG_2ND_TRACK_MUST_BE_UNSELECTED ); angle = 0; } else { angle = NormalizeAngle(angle1-baseAngle); //if ( angle > 90 && angle < 270 ) // angle = NormalizeAngle( angle + 180.0 ); //if ( NormalizeAngle( FindAngle( base, pos1 ) - angle1 ) < 180.0 ) // angle = NormalizeAngle( angle + 180.0 ); /*printf( "angle 1 = %0.3f\n", angle );*/ if ( angle1 > 180.0 ) angle1 -= 180.0; InfoMessage( _("Angle %0.3f"), angle1 ); } GetMovedTracks(TRUE); SetMoveD( FALSE, orig, angle ); } } return C_CONTINUE; case C_MOVE: DYNARR_RESET(trkSeg_t,anchors_da); ep1=-1; ep2=-1; if ( rotateAlignState == 1 ) return C_CONTINUE; if ( rotateAlignState == 2 ) { pos1 = pos; onTrackInSplit = TRUE; trk = OnTrack( &pos, TRUE, FALSE ); onTrackInSplit = FALSE; if ( trk == NULL ) return C_CONTINUE; if ( GetTrkSelected(trk) ) { ErrorMessage( MSG_2ND_TRACK_MUST_BE_UNSELECTED ); return C_CONTINUE; } angle1 = NormalizeAngle( GetAngleAtPoint( trk, pos, NULL, NULL ) ); angle = NormalizeAngle(angle1-baseAngle); if ( angle > 90 && angle < 270 ) angle = NormalizeAngle( angle + 180.0 ); if ( NormalizeAngle( FindAngle( pos, pos1 ) - angle1 ) < 180.0 ) angle = NormalizeAngle( angle + 180.0 ); if ( angle1 > 180.0 ) angle1 -= 180.0; InfoMessage( _("Angle %0.3f"), angle1 ); SetMoveD( FALSE, orig, angle ); /*printf( "angle 2 = %0.3f\n", angle );*/ return C_CONTINUE; } ANGLE_T diff_angle = 0.0; base = pos; drawEnable = enableMoveDraw; if ( FindDistance( orig, pos ) > (20.0/75.0)*mainD.scale ) { ANGLE_T old_angle = angle; angle = FindAngle( orig, pos ); if (!drawnAngle) { baseAngle = angle; drawnAngle = TRUE; direction_set = FALSE; } else { if (!direction_set) { if (DifferenceBetweenAngles(baseAngle,angle)>=0) clockwise = TRUE; else clockwise = FALSE; direction_set = TRUE; } else { if (clockwise) { if (DifferenceBetweenAngles(baseAngle,angle)<0 && fabs(DifferenceBetweenAngles(baseAngle, old_angle))<5) clockwise = FALSE; } else { if (DifferenceBetweenAngles(baseAngle,angle)>=0 && fabs(DifferenceBetweenAngles(baseAngle,old_angle))<5) clockwise = TRUE; } } } orig_base = base = pos; //angle = NormalizeAngle( angle-baseAngle ); diff_angle = DifferenceBetweenAngles(baseAngle,angle); if ( (MyGetKeyState() & (WKEY_CTRL|WKEY_SHIFT)) == (WKEY_CTRL|WKEY_SHIFT) ) { //Both Shift+Ctrl if (clockwise) { if (diff_angle<0) diff_angle+=360; } else { if (diff_angle>0) diff_angle-=360; } diff_angle = floor((diff_angle+7.5)/15.0)*15.0; angle = baseAngle+diff_angle; } Translate( &base, orig, angle, FindDistance(orig,pos) ); //Line one Translate( &orig_base,orig, baseAngle, FindDistance(orig,pos)<=(60.0/75.00*mainD.scale)?FindDistance(orig,pos):60.0/75.00*mainD.scale ); //Line two SetMoveD( FALSE, orig, NormalizeAngle( angle-baseAngle ) ); if (((MyGetKeyState()&(WKEY_ALT)) == WKEY_ALT) != magneticSnap) { //Just Shift if (FindEndIntersection(zero,orig,NormalizeAngle( angle-baseAngle ),&t1,&ep1,&t2,&ep2)) { coOrd pos2 = GetTrkEndPos(t2,ep2); coOrd pos1 = GetTrkEndPos(t1,ep1); Rotate(&pos2,orig,NormalizeAngle( angle-baseAngle )); CreateEndAnchor(pos2,FALSE); CreateEndAnchor(pos1,TRUE); } } #ifdef DRAWCOUNT InfoMessage( _("Angle %0.3f #%ld"), fabs(diff_angle), drawCount ); #else InfoMessage( _("Angle %0.3f %s"), fabs(diff_angle), clockwise?"Clockwise":"Counter-Clockwise" ); #endif wFlush(); drawEnable = TRUE; } else InfoMessage( _("Origin Set. Drag away to set start angle")); return C_CONTINUE; case C_UP: DYNARR_RESET(trkSeg_t,anchors_da); state = 0; if (t1 && ep1>=0 && t2 && ep2>=0) { MoveToJoin(t2,ep2,t1,ep1); CleanSegs(&tempSegs_da); rotateAlignState = 0; } else { if ( rotateAlignState == 1 ) { if ( trk && GetTrkSelected(trk) ) { InfoMessage( _("Align: Click on the 2nd unselected object") ); rotateAlignState = 2; } return C_CONTINUE; } CleanSegs(&tempSegs_da); if ( rotateAlignState == 2 ) { MoveTracks( FALSE, FALSE, TRUE, zero, orig, angle, TRUE ); rotateAlignState = 0; } else if (drawnAngle) { MoveTracks( FALSE, FALSE, TRUE, zero, orig, NormalizeAngle( angle-baseAngle ), TRUE ); } } UndoEnd(); tlist_da.cnt = 0; return C_TERMINATE; case C_CMDMENU: base = pos; trk = OnTrack(&pos, FALSE, FALSE); //Note pollutes pos if turntable if ((trk) && QueryTrack(trk,Q_CAN_ADD_ENDPOINTS)) { //Turntable snap to center if within 1/4 radius trackParams_t trackParams; if (GetTrackParams(PARAMS_CORNU, trk, pos, &trackParams)) { DIST_T dist = FindDistance(base, trackParams.ttcenter); if (dist < trackParams.ttradius/4) { cmdMenuPos = trackParams.ttcenter; } } } moveDescPos = pos; moveDescTrk = trk; SetUpMenu2(pos,trk); menuPos = pos; wMenuPopupShow( selectPopup2M ); return C_CONTINUE; case C_TEXT: if ((action>>8) == 'd') { panCenter = pos; LOG( log_pan, 2, ( "PanCenter:Sel-%d %0.3f %0.3f\n", __LINE__, panCenter.x, panCenter.y ) ); PanHere((void*)0); } if ((action>>8) == 'e') { DoZoomExtents(0); } if ((action>>8) == '0' || (action>>8 == 'o')) { PanMenuEnter('o'); } break; case C_REDRAW: if (anchors_da.cnt) DrawSegs( &tempD, zero, 0.0, &anchors(0), anchors_da.cnt, trackGauge, wDrawColorBlack ); /* DO_REDRAW */ if ( state == 0 ) break; if ( rotateAlignState != 2 ) { DIST_T width = mainD.scale*0.5; DrawLine( &tempD, base, orig, 0, wDrawColorBlue ); if (drawnAngle) { DrawLine( &tempD, orig_base, orig, (wDrawWidth)width, wDrawColorBlue ); ANGLE_T a = DifferenceBetweenAngles(FindAngle(orig, orig_base),FindAngle(orig, base)); DIST_T dist = FindDistance(orig,base); if (dist>(60.0/75.0)*mainD.scale) dist = (60.0/75.0)*mainD.scale; if (direction_set) { if (clockwise) { if (a<0) a = a + 360; DrawArc( &tempD, orig, dist/2, FindAngle(orig,orig_base), a, FALSE, 0, wDrawColorBlue); } else { if (a>0) a = a - 360; DrawArc( &tempD, orig, dist/2, FindAngle(orig,base), fabs(a), FALSE, 0, wDrawColorBlue); } DIST_T d; d = mainD.scale*0.25; ANGLE_T arrow_a = NormalizeAngle(FindAngle(orig,orig_base)+a/2); coOrd arr1,arr2,arr3; Translate(&arr2,orig,arrow_a,dist/2); if (clockwise) arrow_a +=90; else arrow_a -=90; Translate(&arr1,arr2,arrow_a+135,d/2); Translate(&arr3,arr2,arrow_a-135,d/2); DrawLine( &tempD, arr1, arr2, 0, wDrawColorBlue ); DrawLine( &tempD, arr2, arr3, 0, wDrawColorBlue ); } } } DrawMovedTracks(); break; } return C_CONTINUE; } static void QuickMove( void* pos) { coOrd move_pos = *(coOrd*)pos; if ( SelectedTracksAreFrozen() ) return; wDrawDelayUpdate( mainD.d, TRUE ); GetMovedTracks(FALSE); UndoStart( _("Move Tracks"), "Move Tracks" ); MoveTracks( TRUE, TRUE, FALSE, move_pos, zero, 0.0, TRUE ); wDrawDelayUpdate( mainD.d, FALSE ); } static void QuickRotate( void* pangle ) { ANGLE_T angle = (ANGLE_T)(long)pangle; if ( SelectedTracksAreFrozen() ) return; wDrawDelayUpdate( mainD.d, TRUE ); GetMovedTracks(FALSE); //DrawSelectedTracksD( &mainD, wDrawColorWhite ); UndoStart( _("Rotate Tracks"), "Rotate Tracks" ); MoveTracks( TRUE, FALSE, TRUE, zero, cmdMenuPos, (double)angle/1000, TRUE); wDrawDelayUpdate( mainD.d, FALSE ); } static wMenu_p moveDescM; static wMenuToggle_p moveDescMI; static void ChangeDescFlag( wBool_t set, void * mode ) { wDrawDelayUpdate( mainD.d, TRUE ); UndoStart( _("Toggle Label"), "Modedesc( T%d )", GetTrkIndex(moveDescTrk) ); UndoModify( moveDescTrk ); UndrawNewTrack( moveDescTrk ); if ( ( GetTrkBits( moveDescTrk ) & TB_HIDEDESC ) == 0 ) SetTrkBits( moveDescTrk, TB_HIDEDESC ); else ClrTrkBits( moveDescTrk, TB_HIDEDESC ); DrawNewTrack( moveDescTrk ); wDrawDelayUpdate( mainD.d, FALSE ); } track_p FindTrackDescription(coOrd pos, EPINX_T * ep_o, int * mode_o, BOOL_T show_hidden, BOOL_T * hidden_o) { track_p trk = NULL; DIST_T d, dd = 10000; track_p trk1 = NULL; EPINX_T ep1=-1, ep=-1; BOOL_T hidden_t, hidden; coOrd dpos = pos; coOrd cpos; int mode = -1; while ( TrackIterate( &trk1 ) ) { if ( !GetLayerVisible(GetTrkLayer(trk1)) ) continue; if ( (!GetTrkVisible(trk1)) && drawTunnel==0 ) continue; if ( (labelEnable&LABELENABLE_ENDPT_ELEV)!=0 && *mode_o <= 0) { for ( ep1=0; ep1