/** \file layout.c * Layout data and dialog */ /* XTrkCad - Model Railroad CAD * Copyright (C) 2017 Martin Fischer * * 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 "custom.h" #include "i18n.h" #include "layout.h" #include "misc2.h" #include "param.h" #include "paths.h" #include "track.h" #include "wlib.h" #include "fileio.h" #include "utility.h" #define MINTRACKRADIUSPREFS "minTrackRadius" struct sLayoutProps { char title1[TITLEMAXLEN]; char title2[TITLEMAXLEN]; SCALEINX_T curScaleInx; SCALEDESCINX_T curScaleDescInx; GAUGEINX_T curGaugeInx; DIST_T minTrackRadius; DIST_T maxTrackGrade; coOrd roomSize; DynString backgroundFileName; coOrd backgroundPos; ANGLE_T backgroundAngle; int backgroundScreen; double backgroundSize; }; struct sDataLayout { struct sLayoutProps props; DynString fullFileName; struct sLayoutProps *copyOfLayoutProps; }; static struct sDataLayout thisLayout = { { "", "", -1, 0, 0, 0.0, 5.0, {0.0, 0.0}, NaS, {0.0, 0.0}, 0.0, 0, 0.0 }, NaS, NULL, }; static paramFloatRange_t r0_90 = { 0, 90 }; static paramFloatRange_t r1_10000 = { 1, 10000 }; static paramFloatRange_t r1_9999999 = { 1, 9999999 }; static paramFloatRange_t r360_360 = { -360, 360 }; static paramFloatRange_t rN_9999999 = { -99999, 99999 }; static paramIntegerRange_t i0_100 = { 0, 100 }; static void LayoutDlgUpdate(paramGroup_p pg, int inx, void * valueP); /** * Update the full file name. Do not do anything if the new filename is identical to the old one. * * \param filename IN the new filename */ void SetLayoutFullPath(const char *fileName) { if (fileName && fileName[0]) { if (DynStringSize(&thisLayout.fullFileName)) { if (strcmp(DynStringToCStr(&thisLayout.fullFileName),fileName)==0) { return; } DynStringClear(&thisLayout.fullFileName); } DynStringMalloc(&thisLayout.fullFileName, strlen(fileName) + 1); DynStringCatCStr(&thisLayout.fullFileName, fileName); } else { DynStringMalloc(&thisLayout.fullFileName, 2); DynStringCatCStr(&thisLayout.fullFileName, ""); } } /** * Set the minimum radius for the selected scale/gauge into the dialog * * \param scaleName IN name of the scale/gauge eg. HOn3 * \param defaltValue IN default value will be used if no preference is set */ void LoadLayoutMinRadiusPref(char *scaleName, double defaultValue) { DynString prefString = { NULL }; DynStringPrintf(&prefString, MINTRACKRADIUSPREFS "-%s", scaleName); wPrefGetFloat("misc", DynStringToCStr(&prefString), &thisLayout.props.minTrackRadius, defaultValue); DynStringFree(&prefString); } static void CopyLayoutTitle(char* dest, char *src) { strncpy(dest, src, TITLEMAXLEN); *(dest + TITLEMAXLEN - 1) = '\0'; } void SetLayoutTitle(char *title) { CopyLayoutTitle(thisLayout.props.title1, title); } void SetLayoutSubtitle(char *title) { CopyLayoutTitle(thisLayout.props.title2, title); } void SetLayoutMinTrackRadius(DIST_T radius) { thisLayout.props.minTrackRadius = radius; } void SetLayoutMaxTrackGrade(ANGLE_T angle) { thisLayout.props.maxTrackGrade = angle; } void SetLayoutRoomSize(coOrd size) { thisLayout.props.roomSize = size; } void SetLayoutCurScale(SCALEINX_T scale) { thisLayout.props.curScaleInx = scale; } void SetLayoutCurScaleDesc(SCALEDESCINX_T desc) { thisLayout.props.curScaleDescInx = desc; } void SetLayoutCurGauge(GAUGEINX_T gauge) { thisLayout.props.curGaugeInx = gauge; } void SetLayoutBackGroundFullPath(const char *fileName) { if (fileName && fileName[0]) { if (DynStringSize(&thisLayout.props.backgroundFileName)) { if (strcmp(DynStringToCStr(&thisLayout.props.backgroundFileName),fileName)==0) { return; } DynStringClear(&thisLayout.props.backgroundFileName); } DynStringMalloc(&thisLayout.props.backgroundFileName, strlen(fileName) + 1); DynStringCatCStr(&thisLayout.props.backgroundFileName, fileName); } else { DynStringClear(&thisLayout.props.backgroundFileName); DynStringCatCStr(&thisLayout.props.backgroundFileName, ""); } } void SetLayoutBackGroundSize(double size) { thisLayout.props.backgroundSize = size; } void SetLayoutBackGroundPos(coOrd pos) { thisLayout.props.backgroundPos = pos; } void SetLayoutBackGroundAngle(ANGLE_T angle) { thisLayout.props.backgroundAngle = angle; } void SetLayoutBackGroundScreen(int screen) { thisLayout.props.backgroundScreen = screen; } /** * Return the full filename. * * \return pointer to the full filename, should not be modified or freed */ char * GetLayoutFullPath() { char * s = DynStringToCStr(&thisLayout.fullFileName); return s; } /** * Return the filename part of the full path * * \return pointer to the filename part, NULL is no filename is set */ char * GetLayoutFilename() { char *string = DynStringToCStr(&thisLayout.fullFileName); if (string) { return FindFilename(string); } else { return (NULL); } } char * GetLayoutTitle() { return (thisLayout.props.title1); } char * GetLayoutSubtitle() { return (thisLayout.props.title2); } DIST_T GetLayoutMinTrackRadius() { return (thisLayout.props.minTrackRadius); } ANGLE_T GetLayoutMaxTrackGrade() { return (thisLayout.props.maxTrackGrade); } SCALEDESCINX_T GetLayoutCurScaleDesc() { return (thisLayout.props.curScaleDescInx); } SCALEINX_T GetLayoutCurScale() { return (thisLayout.props.curScaleInx); } char * GetLayoutBackGroundFullPath() { char * s = DynStringToCStr(&thisLayout.props.backgroundFileName); return s; } double GetLayoutBackGroundSize() { if (thisLayout.props.backgroundSize > 0.0) { return (thisLayout.props.backgroundSize); } else { return (thisLayout.props.roomSize.x); } } coOrd GetLayoutBackGroundPos() { return (thisLayout.props.backgroundPos); } ANGLE_T GetLayoutBackGroundAngle() { return (thisLayout.props.backgroundAngle); } int GetLayoutBackGroundScreen() { return (thisLayout.props.backgroundScreen); } /**************************************************************************** * * Layout Dialog * */ static char backgroundFileName[STR_LONG_SIZE]; #define TEXT_FIELD_LEN 40 static wWin_p layoutW; /************************************************************************************** * Show only the end of the background file path including the filename in the Dialog */ void SetName() { char * name = GetLayoutBackGroundFullPath(); if (name && name[0]) { //Ignore "" if (name && (strlen(name)<=TEXT_FIELD_LEN)) { for (unsigned int i=0; i<=strlen(name);i++) { backgroundFileName[i] = name[i]; } backgroundFileName[strlen(name)] = '\0'; } else { for (int i=TEXT_FIELD_LEN;i>=0; i--) { backgroundFileName[i] = name[strlen(name)-(TEXT_FIELD_LEN-i)]; } backgroundFileName[TEXT_FIELD_LEN] = '\0'; //Insurance } } else backgroundFileName[0] = '\0'; } static struct wFilSel_t * imageFile_fs; static paramData_p layout_p; static paramGroup_t * layout_pg_p; static wBool_t file_changed; EXPORT BOOL_T haveBackground = FALSE; BOOL_T backgroundVisible = TRUE; char * noname = ""; void BackgroundToggleShow() { backgroundVisible = !backgroundVisible; wButtonSetBusy(backgroundB, backgroundVisible); MainRedraw(); } int GetLayoutBackGroundVisible() { return(backgroundVisible); } /***************************************** * Try to load the background image file */ wBool_t LoadBackGroundImage(void) { char * error; char * background = GetLayoutBackGroundFullPath(); if (wDrawSetBackground( mainD.d, background, &error)==-1) { NoticeMessage(_("Unable to load Image File - %s"),_("Ok"),NULL,error); return FALSE; } return TRUE; } /******************************************************* * Callback from File Select for Background Image File * * \param files number of files selected (only first file is used) * \param fileName array of pointers to filenames * \param data unused * \return FALSE */ EXPORT int LoadImageFile( int files, char ** fileName, void * data ) { if (files >0) { SetLayoutBackGroundFullPath( strdup(fileName[0])); if (!LoadBackGroundImage()) { SetLayoutBackGroundFullPath(noname); backgroundVisible = FALSE; } else { backgroundVisible = TRUE; SetCurrentPath(BACKGROUNDPATHKEY, fileName[0]); } } else { SetLayoutBackGroundFullPath(noname); backgroundVisible = FALSE; } wControlActive((wControl_p)backgroundB, backgroundVisible); wButtonSetBusy(backgroundB, backgroundVisible); SetName(); file_changed = TRUE; ParamLoadControl(layout_pg_p, 8); return FALSE; } /********************************************************** * Save the Background Parms - forcing a write */ void LayoutBackGroundSave(void) { char * background = GetLayoutBackGroundFullPath(); wPrefSetString("layout", "BackgroundPath", background); wPrefSetFloat("layout", "BackgroundPosX", thisLayout.props.backgroundPos.x); wPrefSetFloat("layout", "BackgroundPosY", thisLayout.props.backgroundPos.y); wPrefSetFloat("layout", "BackgroundAngle", thisLayout.props.backgroundAngle); wPrefSetInteger("layout", "BackgroundScreen", thisLayout.props.backgroundScreen); wPrefSetFloat("layout", "BackgroundSize", thisLayout.props.backgroundSize); wPrefFlush(); } /************************************************************ * Run File Select for the Background Image File */ static void ImageFileBrowse( void * junk ) { imageFile_fs = wFilSelCreate( mainW, FS_LOAD, FS_PICTURES, _("Load Background"), sImageFilePattern, LoadImageFile, NULL ); wFilSelect( imageFile_fs, GetCurrentPath( BACKGROUNDPATHKEY ) ); return; } /************************************************************ * Remove the background Image File */ static void ImageFileClear( void * junk) { char * noname = ""; SetLayoutBackGroundFullPath(noname); wDrawSetBackground( mainD.d, NULL, NULL); SetName(); wControlActive((wControl_p)backgroundB, FALSE); file_changed = TRUE; ParamLoadControl(layout_pg_p, 8); MainRedraw(); } static paramData_t layoutPLs[] = { { PD_FLOAT, &thisLayout.props.roomSize.x, "roomsizeX", PDO_NOPREF | PDO_DIM | PDO_NOPSHUPD | PDO_DRAW, &r1_9999999, N_("Room Width"), 0, (void*)(CHANGE_MAIN | CHANGE_MAP) }, { PD_FLOAT, &thisLayout.props.roomSize.y, "roomsizeY", PDO_NOPREF | PDO_DIM | PDO_NOPSHUPD | PDO_DRAW | PDO_DLGHORZ, &r1_9999999, N_(" Height"), 0, (void*)(CHANGE_MAIN | CHANGE_MAP) }, { PD_STRING, &thisLayout.props.title1, "title1", PDO_NOPSHUPD | PDO_STRINGLIMITLENGTH, NULL, N_("Layout Title"), 0, 0, sizeof(thisLayout.props.title1)}, { PD_STRING, &thisLayout.props.title2, "title2", PDO_NOPSHUPD | PDO_STRINGLIMITLENGTH, NULL, N_("Subtitle"), 0, 0, sizeof(thisLayout.props.title2)}, #define SCALEINX (4) { PD_DROPLIST, &thisLayout.props.curScaleDescInx, "scale", PDO_NOPREF | PDO_NOPSHUPD | PDO_NORECORD | PDO_NOUPDACT, (void *)120, N_("Scale"), 0, (void*)(CHANGE_SCALE) }, #define GAUGEINX (5) { PD_DROPLIST, &thisLayout.props.curGaugeInx, "gauge", PDO_NOPREF | PDO_NOPSHUPD | PDO_NORECORD | PDO_NOUPDACT | PDO_DLGHORZ, (void *)120, N_(" Gauge"), 0, (void *)(CHANGE_SCALE) }, #define MINRADIUSENTRY (6) { PD_FLOAT, &thisLayout.props.minTrackRadius, "mintrackradius", PDO_DIM | PDO_NOPSHUPD | PDO_NOPREF, &r1_10000, N_("Min Track Radius"), 0, (void*)(CHANGE_MAIN | CHANGE_LIMITS) }, { PD_FLOAT, &thisLayout.props.maxTrackGrade, "maxtrackgrade", PDO_NOPSHUPD | PDO_DLGHORZ, &r0_90, N_(" Max Track Grade (%)"), 0, (void*)(CHANGE_MAIN) }, #define BACKGROUNDFILEENTRY (8) //Note this value used in the file section routines above - if it chnages, they will need to change { PD_STRING, &backgroundFileName, "backgroundfile", PDO_NOPSHUPD, NULL, N_("Background File Path"), 0, (void *)(CHANGE_BACKGROUND) }, { PD_BUTTON, (void*)ImageFileBrowse, "browse", PDO_DLGHORZ, NULL, N_("Browse ...") }, { PD_BUTTON, (void*)ImageFileClear, "clear", PDO_DLGHORZ, NULL, N_("Clear") }, #define BACKGROUNDPOSX (11) { PD_FLOAT, &thisLayout.props.backgroundPos.x, "backgroundposX", PDO_DIM | PDO_NOPSHUPD | PDO_DRAW, &rN_9999999, N_("Background PosX,Y"), 0, (void*)(CHANGE_BACKGROUND) }, #define BACKGROUNDPOSY (12) { PD_FLOAT, &thisLayout.props.backgroundPos.y, "backgroundposY", PDO_DIM | PDO_NOPSHUPD | PDO_DRAW | PDO_DLGHORZ, &rN_9999999, NULL, 0, (void*)(CHANGE_BACKGROUND) }, #define BACKGROUNDWIDTH (13) { PD_FLOAT, &thisLayout.props.backgroundSize, "backgroundWidth", PDO_DIM | PDO_NOPSHUPD | PDO_DRAW, &r1_9999999, N_("Background Size"), 0, (void*)(CHANGE_BACKGROUND) }, #define BACKGROUNDSCREEN (14) { PD_LONG, &thisLayout.props.backgroundScreen, "backgroundScreen", PDO_NOPSHUPD | PDO_DRAW, &i0_100, N_("Background Screen %"), 0, (void*)(CHANGE_BACKGROUND) }, #define BACKGROUNDANGLE (15) { PD_FLOAT, &thisLayout.props.backgroundAngle, "backgroundAngle", PDO_NOPSHUPD | PDO_DRAW, &r360_360, N_("Background Angle"), 0, (void*)(CHANGE_BACKGROUND) } }; static paramGroup_t layoutPG = { "layout", PGO_RECORD | PGO_PREFMISC, layoutPLs, sizeof layoutPLs / sizeof layoutPLs[0] }; /** * Apply the changes entered to settings * * \param junk IN unused */ static void LayoutOk(void * junk) { long changes; changes = GetChanges(&layoutPG); /* [mf Nov. 15, 2005] Get the gauge/scale settings */ if (changes & CHANGE_SCALE) { SetScaleGauge(thisLayout.props.curScaleDescInx, thisLayout.props.curGaugeInx); } /* [mf Nov. 15, 2005] end */ if (changes & CHANGE_MAP) { SetRoomSize(thisLayout.props.roomSize); } DoChangeNotification(changes); if (changes & CHANGE_LIMITS) { char prefString[30]; // now set the minimum track radius sprintf(prefString, "minTrackRadius-%s", curScaleName); wPrefSetFloat("misc", prefString, thisLayout.props.minTrackRadius); } if ((changes & CHANGE_BACKGROUND) || file_changed) { LayoutBackGroundSave(); file_changed = FALSE; } free(thisLayout.copyOfLayoutProps); wHide(layoutW); MainLayout( TRUE, TRUE ); } /** * Discard the changes entered and replace with earlier values * * \param junk IN unused */ static void LayoutCancel(struct wWin_t *junk) { thisLayout.props = *(thisLayout.copyOfLayoutProps); ParamLoadControls(&layoutPG); LayoutOk(junk); } static void LayoutChange(long changes) { if (changes & (CHANGE_SCALE | CHANGE_UNITS | CHANGE_BACKGROUND)) if (layoutW != NULL && wWinIsVisible(layoutW)) { ParamLoadControls(&layoutPG); } } void DoLayout(void * junk) { SetLayoutRoomSize(mapD.size); if (layoutW == NULL) { layoutW = ParamCreateDialog(&layoutPG, MakeWindowTitle(_("Layout Options")), _("Ok"), LayoutOk, LayoutCancel, TRUE, NULL, 0, LayoutDlgUpdate); LoadScaleList((wList_p)layoutPLs[4].control); } ParamControlActive(&layoutPG, BACKGROUNDFILEENTRY, FALSE); LoadGaugeList((wList_p)layoutPLs[5].control, thisLayout.props.curScaleDescInx); /* set correct gauge list here */ thisLayout.copyOfLayoutProps = malloc(sizeof(struct sLayoutProps)); if (!thisLayout.copyOfLayoutProps) { exit(1); } SetName(); *(thisLayout.copyOfLayoutProps) = thisLayout.props; ParamLoadControls(&layoutPG); wShow(layoutW); } EXPORT addButtonCallBack_t LayoutInit(void) { ParamRegister(&layoutPG); RegisterChangeNotification(LayoutChange); layout_p = layoutPLs; layout_pg_p = &layoutPG; return &DoLayout; } /** * Update the dialog when scale was changed. The list of possible gauges for the selected scale is * updated and the first entry is selected (usually standard gauge). After this the minimum gauge * is set from the preferences. * * \param pg IN dialog * \param inx IN changed entry field * \param valueP IN new value */ static void LayoutDlgUpdate( paramGroup_p pg, int inx, void * valueP) { /* did the scale change ? */ if (inx == SCALEINX) { char prefString[100]; char scaleDesc[100]; LoadGaugeList((wList_p)layoutPLs[GAUGEINX].control, *((int *)valueP)); // set the first entry as default, usually the standard gauge for a scale wListSetIndex((wList_p)layoutPLs[GAUGEINX].control, 0); // get the minimum radius // get the selected scale first wListGetValues((wList_p)layoutPLs[SCALEINX].control, scaleDesc, 99, NULL, NULL); strtok(scaleDesc, " "); // now get the minimum track radius sprintf(prefString, "minTrackRadius-%s", scaleDesc); wPrefGetFloat("misc", prefString, &thisLayout.props.minTrackRadius, 0.0); // put the scale's minimum value into the dialog wStringSetValue((wString_p)layoutPLs[MINRADIUSENTRY].control, FormatDistance(thisLayout.props.minTrackRadius)); } if (inx == BACKGROUNDPOSX) { coOrd pos; pos.x = *(double *)valueP; pos.y = GetLayoutBackGroundPos().y; SetLayoutBackGroundPos(pos); MainRedraw(); } if (inx == BACKGROUNDPOSY) { coOrd pos; pos.y = *(double *)valueP; pos.x = GetLayoutBackGroundPos().x; SetLayoutBackGroundPos(pos); MainRedraw(); } if (inx == BACKGROUNDWIDTH) { SetLayoutBackGroundSize(*(double *)valueP); MainRedraw(); } if (inx == BACKGROUNDSCREEN) { SetLayoutBackGroundScreen(*(int *)valueP); MainRedraw(); } if (inx == BACKGROUNDANGLE) { ANGLE_T angle = NormalizeAngle(*(double *)valueP); wStringSetValue((wString_p)layoutPLs[BACKGROUNDANGLE].control,FormatFloat(angle)); SetLayoutBackGroundAngle(angle); MainRedraw(); } } /*************************************************************************************** * Load Background Options from Saved Parms ***************************************************************************************/ void LayoutBackGroundLoad(void) { SetLayoutBackGroundFullPath(wPrefGetString("layout", "BackgroundPath")); wPrefGetFloat("layout", "BackgroundPosX", &thisLayout.props.backgroundPos.x, 0.0); wPrefGetFloat("layout", "BackgroundPosY", &thisLayout.props.backgroundPos.y, 0.0); wPrefGetFloat("layout", "BackgroundAngle", &thisLayout.props.backgroundAngle, 0.0); long screen_long; wPrefGetInteger("layout", "BackgroundScreen", &screen_long, 0L); thisLayout.props.backgroundScreen = screen_long; wPrefGetFloat("layout", "BackgroundSize", &thisLayout.props.backgroundSize, 0.0); } static wBool_t inited; /************************************************************************************** * Either Clear Background Parms or (if the first time called) Load from Saved Parms **************************************************************************************/ void LayoutBackGroundInit(BOOL_T clear) { if (clear) { SetLayoutBackGroundFullPath(noname); SetLayoutBackGroundPos(zero); SetLayoutBackGroundAngle(0.0); SetLayoutBackGroundScreen(0); SetLayoutBackGroundSize(0.0); LayoutBackGroundSave(); } else { //First Time and not "Clear" inited = TRUE; LayoutBackGroundLoad(); } char * str = GetLayoutBackGroundFullPath(); if (str && str[0]) { if (!LoadBackGroundImage()) { //Failed -> Wipe Out SetLayoutBackGroundFullPath(noname); SetLayoutBackGroundPos(zero); SetLayoutBackGroundAngle(0.0); SetLayoutBackGroundScreen(0); SetLayoutBackGroundSize(0.0); LayoutBackGroundSave(); } } else { wDrawSetBackground( mainD.d, NULL, NULL); } }