summaryrefslogtreecommitdiff
path: root/app/bin/toolbar.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/bin/toolbar.c')
-rw-r--r--app/bin/toolbar.c613
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();
+}