diff options
Diffstat (limited to 'app/bin/toolbar.c')
| -rw-r--r-- | app/bin/toolbar.c | 613 |
1 files changed, 613 insertions, 0 deletions
diff --git a/app/bin/toolbar.c b/app/bin/toolbar.c new file mode 100644 index 0000000..57c3659 --- /dev/null +++ b/app/bin/toolbar.c @@ -0,0 +1,613 @@ +/*****************************************************************//** + * \file toolbar.c + * \brief Toolbar specific functions and data + *********************************************************************/ + +/* XTrackCad - Model Railroad CAD + * Copyright (C) 2005,2023 Dave Bullis, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "common.h" +#include "custom.h" +#include "fileio.h" +#include "param.h" +#include "track.h" +#include "include/toolbar.h" + +EXPORT void ToolbarLayout(void* unused); + +struct sToolbarState { + int previousGroup; // control variable for change control ops + int layerButton; // number of layer controls shown + wWinPix_t nextX; // drawing position for next control + wWinPix_t rowHeight; // height of row +}; + +// local function prototypes +static void InitializeToolbarDialog(void); +static void ToolbarChange(long changes); +static void ToolbarOk(void* unused); +static void ToolbarButtonPlace(struct sToolbarState* tbState, wIndex_t inx); +static void SaveToolbarConfig(void); + +// toolbar properties +static long toolbarSet; +static wWinPix_t toolbarHeight = 0; + +#define TOOLBARSET_INIT (0xFFFF) +#define TOOLBAR_SECTION "misc" +#define TOOLBAR_VARIABLE "toolbarset" + +#define GROUP_DISTANCE (5) // default distance between button groups +#define GROUP_BIG_DISTANCE (GROUP_DISTANCE * 3) // big gap +#define TOOLBAR_MARGIN (20) // left and right margins of toolbar +#define FIXEDLAYERCONTROLS (2) // the layer groups has two controls that are +// always visible (list and background) + +/* +* Bit handling macros +* these macros do not change the passed values but return the result. +* so if you want to change the value it has to be assigned eg. +* bits = SETBIT(bits, 2); +* in order to set bit 2 of the variable "bits" +* +*/ +#define GETBIT(value, bitpos ) ((value) & (1UL << (bitpos))) +#define ISBITSET(value, bitpos ) (((value)&(1UL <<bitpos))!=0) +#define CLEARBIT(value, bitpos ) ((value) & ~(1UL <<(bitpos))) +#define SETBIT(value, bitpos) ((value) | (1UL <<(bitpos))) + +#define ISGROUPVISIBLE(group) ISBITSET(toolbarSet, group) + +// toolbar button list +#define BUTTON_MAX (250) +static struct { + wControl_p control; + wBool_t enabled; + wWinPix_t x, y; + long options; + int group; + wIndex_t cmdInx; +} buttonList[BUTTON_MAX]; +EXPORT int buttonCnt = 0; // TODO-misc-refactor + + +// control the order of the button groups inside the toolbar + +struct buttonGroups { + char* label; // display label + int group; // id of group + bool biggap; // control distance to previous group +}; + +static struct buttonGroups allToolbarGroups[] = { + {N_("File Buttons"), BG_FILE, false}, + {N_("Print Buttons"), BG_PRINT, false}, + {N_("Import/Export Buttons"), BG_EXPORTIMPORT, false}, + {N_("Zoom Buttons"), BG_ZOOM, false}, + {N_("Undo Buttons"), BG_UNDO, false}, + {N_("Easement Button"), BG_EASE, false}, + {N_("SnapGrid Buttons"), BG_SNAP, false}, + {N_("Create Track Buttons"), BG_TRKCRT, true}, + {N_("Layout Control Elements"), BG_CONTROL, true}, + {N_("Modify Track Buttons"), BG_TRKMOD, false}, + {N_("Properties/Select"), BG_SELECT, false}, + {N_("Track Group Buttons"), BG_TRKGRP, false}, + {N_("Train Group Buttons"), BG_TRAIN, true}, + {N_("Create Misc Buttons"), BG_MISCCRT, false}, + {N_("Ruler Button"), BG_RULER, false}, + {N_("Layer Buttons"), BG_LAYER, true}, + {N_("Hot Bar"), BG_HOTBAR}, + {NULL, 0L} +}; + +#define COUNTTOOLBARGROUPS (BG_LAST) + +// toolbar options dialog +static wWin_p toolbarW; +static unsigned long toggleSet; + +// callbacks for button presses +static void SelectAllGroups(void* unused); +static void InvertSelection(void* unused); + +static paramData_t toolbarPLs[] = { + { PD_TOGGLE, &toggleSet, "toolbarset", 0, NULL}, +#define I_SELECTALL (1) + { PD_BUTTON, SelectAllGroups, "selectall", PDO_DLGBOXEND, NULL, N_("Select All") }, +#define I_INVERT (2) + { PD_BUTTON, InvertSelection, "invert", PDO_DLGHORZ, NULL, N_("Invert Selection")} +}; + +static paramGroup_t toolbarPG = { "toolbar", PGO_RECORD, toolbarPLs, + COUNT(toolbarPLs) + }; + +/** + * Initialize the list of available options. The list of labels is created + * from the allToolbarGroups array. Memory allocated here + * is never freed as it might be used when opening the dialog + * + * \param unused + */ + +static void +InitializeToolbarDialog(void) +{ + char** labels = MyMalloc((COUNT(allToolbarGroups)) * sizeof(char*)); + + for (int i = 0; i < COUNT(allToolbarGroups); i++) { + labels[i] = allToolbarGroups[i].label; + } + toolbarPLs[0].winData = labels; + + ParamRegister(&toolbarPG); +} + +static void ToolbarChange(long changes) +{ + if ((changes & CHANGE_TOOLBAR)) { + MainProc(mainW, wResize_e, NULL, NULL); + } +} + +/** + * Handle button press to select all groups. Set all bits to 1, unused bits + * will be ignored + * + * \param unused + */ +static void SelectAllGroups(void* unused) +{ + toggleSet = ~(0UL); + + ParamLoadControls(&toolbarPG); +} + +/** + * Handle button press to invert the current selection. Invert all bits by, + * XOR with 1s, unused bits will be ignored + * + * \param unused + */ +static void InvertSelection(void* unused) +{ + toggleSet ^= ~(0UL); + + ParamLoadControls(&toolbarPG); +} + +/** + * Handle the ok press. The bit pattern set up from the dialog is converted + * to the pattern used by the toolbar. Then the toolbar is refreshed. + * + * \param unused + */ + +static void ToolbarOk(void* unused) +{ + toolbarSet = 0; + + for (int i = 0; i < COUNTTOOLBARGROUPS; i++) { + if (toggleSet & (1UL << i)) { + toolbarSet = SETBIT(toolbarSet, allToolbarGroups[i].group); + } + } + SaveToolbarConfig(); + ToolbarLayout(unused); + MainProc(mainW, wResize_e, NULL, NULL); + wHide(toolbarW); +} + +/** + * When selected from the menu the toolbar config dialog is opened. First lazy + * initialization is done on first call. Then the toggle states are set from + * the toolbar configuration bit pattern and the dialog is shown. + * + * \param unused + */ + +EXPORT void DoToolbar(void* unused) +{ + if (!toolbarW) { + InitializeToolbarDialog(); + toolbarW = ParamCreateDialog(&toolbarPG, + MakeWindowTitle(_("Toolbar Options")), _("OK"), ToolbarOk, ParamCancel_Restore, + TRUE, NULL, 0, NULL); + } + + toggleSet = 0; + for (int i = 0; i < COUNTTOOLBARGROUPS; i++) { + if (ISBITSET(toolbarSet, allToolbarGroups[i].group)) { + toggleSet = SETBIT(toggleSet, i); + } + } + ParamLoadControls(&toolbarPG); + wShow(toolbarW); +} + +/** + * Check whether button group is configured to be visible. + * + * \param group single group to check + * \return true if visible + */ + +EXPORT bool +ToolbarIsGroupVisible(int group) +{ + CHECK(group > 0); + CHECK(group <= COUNTTOOLBARGROUPS); + + return(ISGROUPVISIBLE(group)); +} + +/** + * Get the current height of the toolbar. + * + * \return + */ + +EXPORT wWinPix_t +ToolbarGetHeight(void) +{ + return(toolbarHeight); +} + +/** + * . + */ + +EXPORT void +ToolbarSetHeight(wWinPix_t newHeight) +{ + toolbarHeight = newHeight; +} + +/** + * Buttons are visible when the command is enabled or when additional + * layer buttons need to be shown. + * + * \param inx + */ + +bool +IsButtonVisible(int group, long mode, long options, long layerButtons) +{ + if (group == BG_LAYER) { + if (layerButtons < layerCount+FIXEDLAYERCONTROLS) { + return true; + } else { + return false; + } + } + return(IsCommandEnabled(mode, options)); +} + +/** + * Calculate the position and visibility of a button and display it. + * + * \param inx index into button list + */ + +static void ToolbarButtonPlace(struct sToolbarState *tbState, wIndex_t inx) +{ + wWinPix_t w, h, offset; + wWinPix_t width; + wWinPix_t gap = GROUP_DISTANCE; + int currentGroup = buttonList[inx].group; + + wWinGetSize(mainW, &width, &h); + + if (buttonList[inx].control) { + if (tbState->rowHeight <= 0) { + tbState->rowHeight = wControlGetHeight(buttonList[inx].control); + toolbarHeight = tbState->rowHeight + 5; + } + + if (currentGroup != tbState->previousGroup) { + for (int i = 0; i < COUNTTOOLBARGROUPS; i++) { + if (allToolbarGroups[i].group == currentGroup && + allToolbarGroups[i].biggap) { + gap = GROUP_BIG_DISTANCE; + } + } + } + + if ((ISGROUPVISIBLE(currentGroup)) && + IsButtonVisible(currentGroup, programMode, + buttonList[inx].options, tbState->layerButton )) { + if (currentGroup != tbState->previousGroup) { + tbState->nextX += gap; + tbState->previousGroup = currentGroup; + } + w = wControlGetWidth(buttonList[inx].control); + h = wControlGetHeight(buttonList[inx].control); + if (h < tbState->rowHeight) { + offset = (h - tbState->rowHeight) / 2; + h = tbState->rowHeight; //Uniform + } else { + offset = 0; + } + if (inx < buttonCnt - 1 && + (buttonList[inx + 1].options & IC_ABUT)) { + w += wControlGetWidth(buttonList[inx + 1].control); + } + if (tbState->nextX + w > width - TOOLBAR_MARGIN) { + tbState->nextX = 5; + toolbarHeight += h + 5; + } + if ((currentGroup == BG_LAYER) && + tbState->layerButton >= FIXEDLAYERCONTROLS && + GetLayerHidden(tbState->layerButton - FIXEDLAYERCONTROLS)) { + wControlShow(buttonList[inx].control, FALSE); + tbState->layerButton++; + } else { + wWinPix_t newX = tbState->nextX; + wWinPix_t newY = toolbarHeight - (h + 5 + offset); + + // count number of shown layer buttons + if (currentGroup == BG_LAYER) { + tbState->layerButton++; + } + if ((newX != buttonList[inx].x) || (newY != buttonList[inx].y)) { + wControlShow(buttonList[inx].control, FALSE); + + wControlSetPos(buttonList[inx].control, newX, + newY); + } + buttonList[inx].x = newX; + buttonList[inx].y = newY; + tbState->nextX += wControlGetWidth(buttonList[inx].control); + wControlShow(buttonList[inx].control, TRUE); + } + } else { + wControlShow(buttonList[inx].control, FALSE); + } + } +} + +EXPORT void ToolbarLayout(void* data) +{ + int inx; + struct sToolbarState state = { + .previousGroup = 0, + .nextX = 0, + .layerButton = 0, + .rowHeight = 0, + }; + + for (inx = 0; inx < buttonCnt; inx++) { + ToolbarButtonPlace(&state, inx); + } + + if (ISBITSET(toolbarSet, BG_HOTBAR)) { + LayoutHotBar(data); + } else { + HideHotBar(); + } +} + +/** + * Set the 'pressed' state of a toolbar button. + * + * \param button index into button list + * \param busy desired button state + */ + +EXPORT void ToolbarButtonBusy(wIndex_t button, wBool_t busy) +{ + wButtonSetBusy((wButton_p)buttonList[button].control, + busy); +} + +/** + * Set state of a toolbar button . + * + * \param button index into button list + * \param enable desired state, FALSE if disabled, TRUE if enabled + */ + +EXPORT void ToolbarButtonEnable(wIndex_t button, wBool_t enable) +{ + wControlActive(buttonList[button].control, + enable); +} + +/** + * Enable toolbar buttons that depend on selected track. + * + * \param selected true if any track is selected + */ + +EXPORT void ToolbarButtonEnableIfSelect(bool selected) +{ + for (int inx = 0; inx < buttonCnt; inx++) { + if (buttonList[inx].cmdInx < 0 + && (buttonList[inx].options & IC_SELECTED)) { + ToolbarButtonEnable(inx, selected ); + } + } +} + +/** + * Place a control onto the toolbar. The control is added to the toolbar + * control list and initially hidden. Placement and visibility is controlled + * by ToolbarButtonPlace() + * + * \param control the control to add + * \param options control options + */ + +EXPORT void ToolbarControlAdd(wControl_p control, long options, int cmdGroup) +{ + CHECK(buttonCnt < BUTTON_MAX - 1); + buttonList[buttonCnt].enabled = TRUE; + buttonList[buttonCnt].options = options; + buttonList[buttonCnt].group = cmdGroup; + buttonList[buttonCnt].x = 0; + buttonList[buttonCnt].y = 0; + buttonList[buttonCnt].control = control; + buttonList[buttonCnt].cmdInx = -1; + wControlShow(control, FALSE); + buttonCnt++; +} + +/** + * Link a command to a specific toolbar button. + * + * \param button the button + * \param command command to activate when button is pressed + * \return + */ + +EXPORT void ToolbarButtonCommandLink(wIndex_t button, int command) +{ + if (button >= 0 && buttonList[button].cmdInx == -1) { + // set button back-link + buttonList[button].cmdInx = commandCnt; + } +} + +/** + * Update the toolbar button for selected command eg. circle vs. filled + * circle. + * + * \param button toolbar button + * \param command current command + * \param icon new icon + * \param helpKey new help key + * \param context new command context + */ + +EXPORT void ToolbarUpdateButton(wIndex_t button, wIndex_t command, + char * icon, + const char * helpKey, + void * context) +{ + if (buttonList[button].cmdInx != command) { + wButtonSetLabel((wButton_p) buttonList[button].control,icon); + wControlSetHelp(buttonList[button].control, + GetBalloonHelpStr(helpKey)); + wControlSetContext(buttonList[button].control, + context); + buttonList[button].cmdInx = command; + } +} + +/*--------------------------------------------------------------------*/ + +/** + * Handle simulated button press during playbook. + * + * \param buttInx selected button + */ +EXPORT void PlaybackButtonMouse(wIndex_t buttInx) +{ + wWinPix_t cmdX, cmdY; + coOrd pos; + + if (buttInx < 0 || buttInx >= buttonCnt) { + return; + } + if (buttonList[buttInx].control == NULL) { + return; + } + cmdX = buttonList[buttInx].x + 17; + cmdY = toolbarHeight - (buttonList[buttInx].y + 17) + + (wWinPix_t)(mainD.size.y / mainD.scale * mainD.dpi) + 30; + + mainD.Pix2CoOrd(&mainD, cmdX, cmdY, &pos); + MovePlaybackCursor(&mainD, pos, TRUE, buttonList[buttInx].control); + if (playbackTimer == 0) { + wButtonSetBusy((wButton_p)buttonList[buttInx].control, TRUE); + wFlush(); + wPause(500); + wButtonSetBusy((wButton_p)buttonList[buttInx].control, FALSE); + wFlush(); + } +} + +/** + * Handle cursor positioning for toolbar buttons during playback . + * + * \param buttonInx + */ + +EXPORT void ToolbarButtonPlayback(wIndex_t buttonInx) +{ + wWinPix_t cmdX, cmdY; + coOrd pos; + + cmdX = buttonList[buttonInx].x + 17; + cmdY = toolbarHeight - (buttonList[buttonInx].y + 17) + + (wWinPix_t)(mainD.size.y / mainD.scale * mainD.dpi) + 30; + mainD.Pix2CoOrd(&mainD, cmdX, cmdY, &pos); + MovePlaybackCursor(&mainD, pos, TRUE, buttonList[buttonInx].control); +} + +/** + * Save the toolbar setting to the configuration file. + * + */ + +static void +SaveToolbarConfig(void) +{ + wPrefSetInteger(TOOLBAR_SECTION, TOOLBAR_VARIABLE, toolbarSet); + if (recordF) + fprintf(recordF, "PARAMETER %s %s %ld", TOOLBAR_SECTION, + TOOLBAR_VARIABLE, toolbarSet); + +} + +/** + * Get the preferences for the toolbar from the configuration file. + * Bits unused are cleared just to be sure; + * + */ + +EXPORT void +ToolbarLoadConfig(void) +{ + unsigned long maxToolbarSet = (1 << COUNTTOOLBARGROUPS) - 1; + long toolbarSetIni; + + wPrefGetInteger(TOOLBAR_SECTION, TOOLBAR_VARIABLE, &toolbarSetIni, + TOOLBARSET_INIT); + + toolbarSet = (unsigned long)toolbarSetIni & maxToolbarSet; + + // unused but saved to stay compatible + wPrefSetInteger(TOOLBAR_SECTION, "max-toolbarset", maxToolbarSet); + + if (recordF) + fprintf(recordF, "PARAMETER %s %s %lX -> %lX", TOOLBAR_SECTION, + TOOLBAR_VARIABLE, toolbarSetIni, toolbarSet); +} + +/** + * Initialize toolbar functions. + * + */ + +EXPORT void InitToolbar(void) +{ + RegisterChangeNotification(ToolbarChange); + + ToolbarLoadConfig(); +} |
