diff options
31 files changed, 321 insertions, 89 deletions
@@ -22,18 +22,20 @@ devices (ie. without a proper keyboard) much more practical. * There is now a key binding to change the multiplayer spy key (usually F12). - * There is now a configuration file parameter to set the OPL I/O - port, for cards that don't use port 0x388. + * The setup tool now has a "warp" button on the main menu, like + Vanilla setup.exe (thanks Proteh). * Up to 8 mouse buttons are now supported (including the mousewheel). + * A new command line parameter has been added (-solo-net) which + can be used to simulate being in a single player netgame. + * There is now a configuration file parameter to set the OPL I/O + port, for cards that don't use port 0x388. * The Python scripts used for building Chocolate Doom now work with Python 3 (but also continue to work with Python 2) (thanks arin). - * The font used for the textscreen library can be forced by - setting the TEXTSCREEN_FONT environment variable to "small" or - "normal". * There is now a NOT-BUGS file included that lists some common - Vanilla Doom bugs/limitations that you might encounter. + Vanilla Doom bugs/limitations that you might encounter + (thanks to Sander van Dijk for feedback). Compatibility: * The -timer and -avg options now work the same as Vanilla when @@ -43,6 +45,11 @@ * The HacX v1.2 IWAD file is now supported, and can be used standalone without the need for the Doom II IWAD (thanks atyth). + * The I_Error function doesn't display "Error:" before the error + message, matching the Vanilla behavior. "Error" has also been + removed from the title of the dialog box that appears on + Windows when this happens. This is desirable as not all such + messages are actually errors (thanks Proteh). Bugs fixed: * A workaround has been a bug in old versions of SDL_mixer @@ -70,6 +77,17 @@ * Screen borders no longer flash when running on widescreen monitors, if you choose a true-color screen mode (thanks exp(x)). + * The controller player in a netgame is the first player to join, + instead of just being someone who gets lucky. + + libtextscreen: + * The font used for the textscreen library can be forced by + setting the TEXTSCREEN_FONT environment variable to "small" or + "normal". + * Tables or scroll panes that don't contain any selectable widgets + are now themselves not selectable (thanks Proteh). + * The actions displayed at the bottom of windows are now laid out + in a more aesthetically pleasing way. 1.4.0 (2010-07-10): @@ -65,7 +65,7 @@ If you are playing on a particularly large level, it is possible that when you save the game, the game will quit with the message "Savegame buffer overrun". -Vanilla Doom has a limited size memory bufferthat it uses for saving +Vanilla Doom has a limited size memory buffer that it uses for saving games. If you are playing on a large level, the buffer may be too small for the entire savegame to fit. Chocolate Doom allows the limit to be disabled: in the setup tool, go to the "compatibility" menu and diff --git a/codeblocks/server.cbp b/codeblocks/server.cbp index a34a66dd..8c8adfb8 100644 --- a/codeblocks/server.cbp +++ b/codeblocks/server.cbp @@ -84,6 +84,10 @@ <Option compilerVar="CC" /> </Unit> <Unit filename="..\src\net_packet.h" /> + <Unit filename="..\src\net_query.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="..\src\net_query.h" /> <Unit filename="..\src\net_sdl.c"> <Option compilerVar="CC" /> </Unit> diff --git a/src/doom/d_net.c b/src/doom/d_net.c index b307d97f..70359ebd 100644 --- a/src/doom/d_net.c +++ b/src/doom/d_net.c @@ -204,8 +204,8 @@ void NetUpdate (void) G_BuildTiccmd(&cmd); #ifdef FEATURE_MULTIPLAYER - - if (netgame && !demoplayback) + + if (net_client_connected) { NET_CL_SendTiccmd(&cmd, maketic); } @@ -460,6 +460,19 @@ void D_InitSinglePlayerGame(void) recvtic = 0; playeringame[0] = true; + + //! + // @category net + // + // Start the game playing as though in a netgame with a single + // player. This can also be used to play back single player netgame + // demos. + // + + if (M_CheckParm("-solo-net") > 0) + { + netgame = true; + } } boolean D_InitNetGame(net_connect_data_t *connect_data, @@ -468,6 +481,7 @@ boolean D_InitNetGame(net_connect_data_t *connect_data, net_addr_t *addr = NULL; int i; + #ifdef FEATURE_MULTIPLAYER //! diff --git a/src/doom/g_game.c b/src/doom/g_game.c index 0aaf8ee7..5d30899d 100644 --- a/src/doom/g_game.c +++ b/src/doom/g_game.c @@ -2145,16 +2145,11 @@ void G_DoPlayDemo (void) for (i=0 ; i<MAXPLAYERS ; i++) playeringame[i] = *demo_p++; - //! - // @category demo - // - // Play back a demo recorded in a netgame with a single player. - // - - if (playeringame[1] || M_CheckParm("-netdemo") > 0) - { - netgame = true; - netdemo = true; + if (playeringame[1] || M_CheckParm("-solo-net") > 0 + || M_CheckParm("-netdemo") > 0) + { + netgame = true; + netdemo = true; } // don't spend a lot of time in loadlevel diff --git a/src/i_system.c b/src/i_system.c index cd8ddcee..18dfb415 100644 --- a/src/i_system.c +++ b/src/i_system.c @@ -327,9 +327,9 @@ void I_Error (char *error, ...) // Message first. va_start(argptr, error); - fprintf(stderr, "\nError: "); + //fprintf(stderr, "\nError: "); vfprintf(stderr, error, argptr); - fprintf(stderr, "\n"); + fprintf(stderr, "\n\n"); va_end(argptr); fflush(stderr); @@ -362,7 +362,7 @@ void I_Error (char *error, ...) msgbuf, strlen(msgbuf) + 1, wmsgbuf, sizeof(wmsgbuf)); - MessageBoxW(NULL, wmsgbuf, L"Error", MB_OK); + MessageBoxW(NULL, wmsgbuf, L"", MB_OK); } #endif diff --git a/src/net_gui.c b/src/net_gui.c index 7473c948..f2c4f1e5 100644 --- a/src/net_gui.c +++ b/src/net_gui.c @@ -287,7 +287,7 @@ void NET_WaitForStart(net_gamesettings_t *settings) if (!net_client_connected) { - I_Error("Disconnected from server"); + I_Error("Lost connection to server"); } TXT_Sleep(100); diff --git a/src/net_query.c b/src/net_query.c index 0c69c231..ae56dea6 100644 --- a/src/net_query.c +++ b/src/net_query.c @@ -39,7 +39,7 @@ // DNS address of the Internet master server. -#define MASTER_SERVER_ADDRESS "master.chocolate-doom.org" +#define MASTER_SERVER_ADDRESS "master.chocolate-doom.org:2342" // Time to wait for a response before declaring a timeout. diff --git a/src/net_server.c b/src/net_server.c index 3d5df135..f3c16c1b 100644 --- a/src/net_server.c +++ b/src/net_server.c @@ -70,6 +70,11 @@ typedef struct int last_send_time; char *name; + // Time that this client connected to the server. + // This is used to determine the controller (oldest client). + + unsigned int connect_time; + // Last time new gamedata was received from this client int last_gamedata_time; @@ -382,19 +387,29 @@ static void NET_SV_AdvanceWindow(void) static net_client_t *NET_SV_Controller(void) { + net_client_t *best; int i; - // first client in the list is the controller + // Find the oldest client (first to connect). + + best = NULL; for (i=0; i<MAXNETNODES; ++i) { - if (ClientConnected(&clients[i]) && !clients[i].drone) + // Can't be controller? + + if (!ClientConnected(&clients[i]) || clients[i].drone) { - return &clients[i]; + continue; + } + + if (best == NULL || clients[i].connect_time < best->connect_time) + { + best = &clients[i]; } } - return NULL; + return best; } // Given an address, find the corresponding client @@ -434,6 +449,7 @@ static void NET_SV_InitNewClient(net_client_t *client, char *player_name) { client->active = true; + client->connect_time = I_GetTimeMS(); NET_Conn_InitServer(&client->connection, addr); client->addr = addr; client->last_send_time = -1; diff --git a/src/setup/mainmenu.c b/src/setup/mainmenu.c index c3cb7db5..ffa174de 100644 --- a/src/setup/mainmenu.c +++ b/src/setup/mainmenu.c @@ -189,6 +189,7 @@ void MainMenu(void) { txt_window_t *window; txt_window_action_t *quit_action; + txt_window_action_t *warp_action; window = TXT_NewWindow("Main Menu"); @@ -230,8 +231,12 @@ void MainMenu(void) NULL); quit_action = TXT_NewWindowAction(KEY_ESCAPE, "Quit"); + warp_action = TXT_NewWindowAction(KEY_F1, "Warp"); TXT_SignalConnect(quit_action, "pressed", QuitConfirm, NULL); + TXT_SignalConnect(warp_action, "pressed", + (TxtWidgetSignalFunc) WarpMenu, NULL); TXT_SetWindowAction(window, TXT_HORIZ_LEFT, quit_action); + TXT_SetWindowAction(window, TXT_HORIZ_CENTER, warp_action); TXT_SetKeyListener(window, MainMenuKeyPress, NULL); } diff --git a/src/setup/multiplayer.c b/src/setup/multiplayer.c index 24cd0670..0d88221f 100644 --- a/src/setup/multiplayer.c +++ b/src/setup/multiplayer.c @@ -209,7 +209,11 @@ static void AddIWADParameter(execute_context_t *exec) } } -static void StartGame(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data)) +// Callback function invoked to launch the game. +// This is used when starting a server and also when starting a +// single player game via the "warp" menu. + +static void StartGame(int multiplayer) { execute_context_t *exec; @@ -221,7 +225,6 @@ static void StartGame(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data)) AddExtraParameters(exec); AddIWADParameter(exec); - AddCmdLineParameter(exec, "-server"); AddCmdLineParameter(exec, "-skill %i", skill + 1); if (gamemission == hexen) @@ -244,20 +247,6 @@ static void StartGame(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data)) AddCmdLineParameter(exec, "-respawn"); } - if (deathmatch == 1) - { - AddCmdLineParameter(exec, "-deathmatch"); - } - else if (deathmatch == 2) - { - AddCmdLineParameter(exec, "-altdeath"); - } - - if (timer > 0) - { - AddCmdLineParameter(exec, "-timer %i", timer); - } - if (warptype == WARP_ExMy) { // TODO: select IWAD based on warp type @@ -268,7 +257,27 @@ static void StartGame(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data)) AddCmdLineParameter(exec, "-warp %i", warpmap); } - AddCmdLineParameter(exec, "-port %i", udpport); + // Multiplayer-specific options: + + if (multiplayer) + { + AddCmdLineParameter(exec, "-server"); + AddCmdLineParameter(exec, "-port %i", udpport); + + if (deathmatch == 1) + { + AddCmdLineParameter(exec, "-deathmatch"); + } + else if (deathmatch == 2) + { + AddCmdLineParameter(exec, "-altdeath"); + } + + if (timer > 0) + { + AddCmdLineParameter(exec, "-timer %i", timer); + } + } AddWADs(exec); @@ -282,6 +291,16 @@ static void StartGame(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(user_data)) exit(0); } +static void StartServerGame(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused)) +{ + StartGame(1); +} + +static void StartSinglePlayerGame(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused)) +{ + StartGame(0); +} + static void UpdateWarpButton(void) { char buf[10]; @@ -544,12 +563,27 @@ static txt_widget_t *IWADSelector(void) return result; } -static txt_window_action_t *StartGameAction(void) +// Create the window action button to start the game. This invokes +// a different callback depending on whether to start a multiplayer +// or single player game. + +static txt_window_action_t *StartGameAction(int multiplayer) { txt_window_action_t *action; + TxtWidgetSignalFunc callback; action = TXT_NewWindowAction(KEY_F10, "Start"); - TXT_SignalConnect(action, "pressed", StartGame, NULL); + + if (multiplayer) + { + callback = StartServerGame; + } + else + { + callback = StartSinglePlayerGame; + } + + TXT_SignalConnect(action, "pressed", callback, NULL); return action; } @@ -591,7 +625,11 @@ static txt_window_action_t *WadWindowAction(void) return action; } -void StartMultiGame(void) +// "Start game" menu. This is used for the start server window +// and the single player warp menu. The parameters specify +// the window title and whether to display multiplayer options. + +static void StartGameMenu(char *window_title, int multiplayer) { txt_window_t *window; txt_table_t *gameopt_table; @@ -599,7 +637,7 @@ void StartMultiGame(void) txt_widget_t *iwad_selector; int num_mult_types = 2; - window = TXT_NewWindow("Start multiplayer game"); + window = TXT_NewWindow(window_title); TXT_AddWidgets(window, gameopt_table = TXT_NewTable(2), @@ -614,7 +652,7 @@ void StartMultiGame(void) NULL); TXT_SetWindowAction(window, TXT_HORIZ_CENTER, WadWindowAction()); - TXT_SetWindowAction(window, TXT_HORIZ_RIGHT, StartGameAction()); + TXT_SetWindowAction(window, TXT_HORIZ_RIGHT, StartGameAction(multiplayer)); TXT_SetColumnWidths(gameopt_table, 12, 12); @@ -632,14 +670,8 @@ void StartMultiGame(void) iwad_selector = IWADSelector(), TXT_NewLabel("Skill"), skillbutton = TXT_NewDropdownList(&skill, doom_skills, 5), - TXT_NewLabel("Game type"), - TXT_NewDropdownList(&deathmatch, gamemodes, num_mult_types), TXT_NewLabel("Level warp"), warpbutton = TXT_NewButton2("????", LevelSelectDialog, NULL), - TXT_NewLabel("Time limit"), - TXT_NewHorizBox(TXT_NewIntInputBox(&timer, 2), - TXT_NewLabel("minutes"), - NULL), NULL); if (gamemission == hexen) @@ -651,19 +683,41 @@ void StartMultiGame(void) NULL); } + if (multiplayer) + { + TXT_AddWidgets(gameopt_table, + TXT_NewLabel("Game type"), + TXT_NewDropdownList(&deathmatch, gamemodes, num_mult_types), + TXT_NewLabel("Time limit"), + TXT_NewHorizBox(TXT_NewIntInputBox(&timer, 2), + TXT_NewLabel("minutes"), + NULL), + NULL); + + TXT_AddWidgets(advanced_table, + TXT_NewLabel("UDP port"), + TXT_NewIntInputBox(&udpport, 5), + NULL); + } + TXT_SetColumnWidths(advanced_table, 12, 12); TXT_SignalConnect(iwad_selector, "changed", UpdateWarpType, NULL); - TXT_AddWidgets(advanced_table, - TXT_NewLabel("UDP port"), - TXT_NewIntInputBox(&udpport, 5), - NULL); - UpdateWarpType(NULL, NULL); UpdateWarpButton(); } +void StartMultiGame(void) +{ + StartGameMenu("Start multiplayer game", 1); +} + +void WarpMenu(void) +{ + StartGameMenu("Level Warp", 0); +} + static void DoJoinGame(void *unused1, void *unused2) { execute_context_t *exec; diff --git a/src/setup/multiplayer.h b/src/setup/multiplayer.h index 7490bc3c..afc8a2a8 100644 --- a/src/setup/multiplayer.h +++ b/src/setup/multiplayer.h @@ -23,6 +23,7 @@ #define SETUP_MULTIPLAYER_H void StartMultiGame(void); +void WarpMenu(void); void JoinMultiGame(void); void MultiplayerConfig(void); diff --git a/src/setup/txt_joybinput.c b/src/setup/txt_joybinput.c index 1e132962..cde3d2c2 100644 --- a/src/setup/txt_joybinput.c +++ b/src/setup/txt_joybinput.c @@ -206,6 +206,7 @@ static void TXT_JoystickInputMousePress(TXT_UNCAST_ARG(widget), int x, int y, in txt_widget_class_t txt_joystick_input_class = { + TXT_AlwaysSelectable, TXT_JoystickInputSizeCalc, TXT_JoystickInputDrawer, TXT_JoystickInputKeyPress, diff --git a/src/setup/txt_keyinput.c b/src/setup/txt_keyinput.c index 483c325f..08eb9d8c 100644 --- a/src/setup/txt_keyinput.c +++ b/src/setup/txt_keyinput.c @@ -171,6 +171,7 @@ static void TXT_KeyInputMousePress(TXT_UNCAST_ARG(widget), int x, int y, int b) txt_widget_class_t txt_key_input_class = { + TXT_AlwaysSelectable, TXT_KeyInputSizeCalc, TXT_KeyInputDrawer, TXT_KeyInputKeyPress, diff --git a/src/setup/txt_mouseinput.c b/src/setup/txt_mouseinput.c index 8b87e651..4f454c8c 100644 --- a/src/setup/txt_mouseinput.c +++ b/src/setup/txt_mouseinput.c @@ -164,6 +164,7 @@ static void TXT_MouseInputMousePress(TXT_UNCAST_ARG(widget), int x, int y, int b txt_widget_class_t txt_mouse_input_class = { + TXT_AlwaysSelectable, TXT_MouseInputSizeCalc, TXT_MouseInputDrawer, TXT_MouseInputKeyPress, diff --git a/textscreen/examples/guitest.c b/textscreen/examples/guitest.c index 5a931949..df79be2d 100644 --- a/textscreen/examples/guitest.c +++ b/textscreen/examples/guitest.c @@ -163,8 +163,8 @@ void Window2(void) { txt_window_t *window; txt_table_t *table; + txt_table_t *unselectable_table; txt_scrollpane_t *scrollpane; - int i; window = TXT_NewWindow("Another test"); TXT_SetWindowPosition(window, @@ -172,10 +172,13 @@ void Window2(void) TXT_VERT_TOP, TXT_SCREEN_W - 1, 1); - for (i=0; i<5; ++i) - { - TXT_AddWidget(window, TXT_NewButton("hello there blah blah blah blah")); - } + TXT_AddWidgets(window, + TXT_NewScrollPane(40, 1, + TXT_NewLabel("* Unselectable scroll pane *")), + unselectable_table = TXT_NewTable(1), + NULL); + + TXT_AddWidget(unselectable_table, TXT_NewLabel("* Unselectable table *")); TXT_AddWidget(window, TXT_NewSeparator("Input boxes")); table = TXT_NewTable(2); diff --git a/textscreen/txt_button.c b/textscreen/txt_button.c index a7a2d25a..85517b3d 100644 --- a/textscreen/txt_button.c +++ b/textscreen/txt_button.c @@ -96,6 +96,7 @@ static void TXT_ButtonMousePress(TXT_UNCAST_ARG(button), int x, int y, int b) txt_widget_class_t txt_button_class = { + TXT_AlwaysSelectable, TXT_ButtonSizeCalc, TXT_ButtonDrawer, TXT_ButtonKeyPress, diff --git a/textscreen/txt_checkbox.c b/textscreen/txt_checkbox.c index 0cb06bad..75afa986 100644 --- a/textscreen/txt_checkbox.c +++ b/textscreen/txt_checkbox.c @@ -117,6 +117,7 @@ static void TXT_CheckBoxMousePress(TXT_UNCAST_ARG(checkbox), int x, int y, int b txt_widget_class_t txt_checkbox_class = { + TXT_AlwaysSelectable, TXT_CheckBoxSizeCalc, TXT_CheckBoxDrawer, TXT_CheckBoxKeyPress, diff --git a/textscreen/txt_dropdown.c b/textscreen/txt_dropdown.c index efed8d67..c8103302 100644 --- a/textscreen/txt_dropdown.c +++ b/textscreen/txt_dropdown.c @@ -262,6 +262,7 @@ static void TXT_DropdownListMousePress(TXT_UNCAST_ARG(list), txt_widget_class_t txt_dropdown_list_class = { + TXT_AlwaysSelectable, TXT_DropdownListSizeCalc, TXT_DropdownListDrawer, TXT_DropdownListKeyPress, diff --git a/textscreen/txt_inputbox.c b/textscreen/txt_inputbox.c index 3e52bae9..852346f3 100644 --- a/textscreen/txt_inputbox.c +++ b/textscreen/txt_inputbox.c @@ -232,6 +232,7 @@ static void TXT_InputBoxMousePress(TXT_UNCAST_ARG(inputbox), txt_widget_class_t txt_inputbox_class = { + TXT_AlwaysSelectable, TXT_InputBoxSizeCalc, TXT_InputBoxDrawer, TXT_InputBoxKeyPress, @@ -242,6 +243,7 @@ txt_widget_class_t txt_inputbox_class = txt_widget_class_t txt_int_inputbox_class = { + TXT_AlwaysSelectable, TXT_InputBoxSizeCalc, TXT_InputBoxDrawer, TXT_IntInputBoxKeyPress, diff --git a/textscreen/txt_label.c b/textscreen/txt_label.c index 7ae29c3d..0deea803 100644 --- a/textscreen/txt_label.c +++ b/textscreen/txt_label.c @@ -104,6 +104,7 @@ static void TXT_LabelDestructor(TXT_UNCAST_ARG(label)) txt_widget_class_t txt_label_class = { + TXT_NeverSelectable, TXT_LabelSizeCalc, TXT_LabelDrawer, NULL, @@ -170,7 +171,6 @@ txt_label_t *TXT_NewLabel(char *text) label = malloc(sizeof(txt_label_t)); TXT_InitWidget(label, &txt_label_class); - label->widget.selectable = 0; label->label = NULL; label->lines = NULL; diff --git a/textscreen/txt_radiobutton.c b/textscreen/txt_radiobutton.c index 7ede7211..efebbf9e 100644 --- a/textscreen/txt_radiobutton.c +++ b/textscreen/txt_radiobutton.c @@ -121,6 +121,7 @@ static void TXT_RadioButtonMousePress(TXT_UNCAST_ARG(radiobutton), txt_widget_class_t txt_radiobutton_class = { + TXT_AlwaysSelectable, TXT_RadioButtonSizeCalc, TXT_RadioButtonDrawer, TXT_RadioButtonKeyPress, diff --git a/textscreen/txt_scrollpane.c b/textscreen/txt_scrollpane.c index d81cce4b..17c9bcbf 100644 --- a/textscreen/txt_scrollpane.c +++ b/textscreen/txt_scrollpane.c @@ -138,7 +138,7 @@ static void TXT_ScrollPaneSizeCalc(TXT_UNCAST_ARG(scrollpane)) } if (scrollpane->expand_h) { - scrollpane->h = FullWidth(scrollpane); + scrollpane->h = FullHeight(scrollpane); } scrollpane->widget.w = scrollpane->w; @@ -486,8 +486,26 @@ static void TXT_ScrollPaneLayout(TXT_UNCAST_ARG(scrollpane)) } } +static int TXT_ScrollPaneSelectable(TXT_UNCAST_ARG(scrollpane)) +{ + TXT_CAST_ARG(txt_scrollpane_t, scrollpane); + + // If scroll bars are displayed, the scroll pane must be selectable + // so that we can use the arrow keys to scroll around. + + if (NeedsScrollbars(scrollpane)) + { + return 1; + } + + // Otherwise, whether this is selectable depends on the child widget. + + return TXT_SelectableWidget(scrollpane->child); +} + txt_widget_class_t txt_scrollpane_class = { + TXT_ScrollPaneSelectable, TXT_ScrollPaneSizeCalc, TXT_ScrollPaneDrawer, TXT_ScrollPaneKeyPress, diff --git a/textscreen/txt_separator.c b/textscreen/txt_separator.c index 2bf74b8f..6b779626 100644 --- a/textscreen/txt_separator.c +++ b/textscreen/txt_separator.c @@ -82,6 +82,7 @@ static void TXT_SeparatorDestructor(TXT_UNCAST_ARG(separator)) txt_widget_class_t txt_separator_class = { + TXT_NeverSelectable, TXT_SeparatorSizeCalc, TXT_SeparatorDrawer, NULL, @@ -97,7 +98,6 @@ txt_separator_t *TXT_NewSeparator(char *label) separator = malloc(sizeof(txt_separator_t)); TXT_InitWidget(separator, &txt_separator_class); - separator->widget.selectable = 0; if (label != NULL) { diff --git a/textscreen/txt_spinctrl.c b/textscreen/txt_spinctrl.c index 2b99f535..d775aecf 100644 --- a/textscreen/txt_spinctrl.c +++ b/textscreen/txt_spinctrl.c @@ -358,6 +358,7 @@ static void TXT_SpinControlMousePress(TXT_UNCAST_ARG(spincontrol), txt_widget_class_t txt_spincontrol_class = { + TXT_AlwaysSelectable, TXT_SpinControlSizeCalc, TXT_SpinControlDrawer, TXT_SpinControlKeyPress, diff --git a/textscreen/txt_strut.c b/textscreen/txt_strut.c index e7fe6328..f3a618f3 100644 --- a/textscreen/txt_strut.c +++ b/textscreen/txt_strut.c @@ -55,6 +55,7 @@ static int TXT_StrutKeyPress(TXT_UNCAST_ARG(strut), int key) txt_widget_class_t txt_strut_class = { + TXT_NeverSelectable, TXT_StrutSizeCalc, TXT_StrutDrawer, TXT_StrutKeyPress, @@ -70,7 +71,6 @@ txt_strut_t *TXT_NewStrut(int width, int height) strut = malloc(sizeof(txt_strut_t)); TXT_InitWidget(strut, &txt_strut_class); - strut->widget.selectable = 0; strut->width = width; strut->height = height; diff --git a/textscreen/txt_table.c b/textscreen/txt_table.c index 1b432681..ffe6fd14 100644 --- a/textscreen/txt_table.c +++ b/textscreen/txt_table.c @@ -202,7 +202,7 @@ void TXT_AddWidgets(TXT_UNCAST_ARG(table), ...) va_end(args); } -static int SelectableWidget(txt_table_t *table, int x, int y) +static int SelectableCell(txt_table_t *table, int x, int y) { txt_widget_t *widget; int i; @@ -217,7 +217,9 @@ static int SelectableWidget(txt_table_t *table, int x, int y) if (i >= 0 && i < table->num_widgets) { widget = table->widgets[i]; - return widget != NULL && widget->selectable && widget->visible; + return widget != NULL + && TXT_SelectableWidget(widget) + && widget->visible; } return 0; @@ -237,14 +239,14 @@ static int FindSelectableColumn(txt_table_t *table, int row, int start_col) { // Search to the right - if (SelectableWidget(table, start_col + x, row)) + if (SelectableCell(table, start_col + x, row)) { return start_col + x; } // Search to the left - if (SelectableWidget(table, start_col - x, row)) + if (SelectableCell(table, start_col - x, row)) { return start_col - x; } @@ -270,7 +272,7 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key) if (selected >= 0 && selected < table->num_widgets) { if (table->widgets[selected] != NULL - && table->widgets[selected]->selectable + && TXT_SelectableWidget(table->widgets[selected]) && TXT_WidgetKeyPress(table->widgets[selected], key)) { return 1; @@ -329,7 +331,7 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key) for (new_x = table->selected_x - 1; new_x >= 0; --new_x) { - if (SelectableWidget(table, new_x, table->selected_y)) + if (SelectableCell(table, new_x, table->selected_y)) { // Found a selectable widget! @@ -348,7 +350,7 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key) for (new_x = table->selected_x + 1; new_x < table->columns; ++new_x) { - if (SelectableWidget(table, new_x, table->selected_y)) + if (SelectableCell(table, new_x, table->selected_y)) { // Found a selectable widget! @@ -547,7 +549,7 @@ static void TXT_TableMousePress(TXT_UNCAST_ARG(table), int x, int y, int b) // Select the cell if the widget is selectable - if (widget->selectable) + if (TXT_SelectableWidget(widget)) { table->selected_x = i % table->columns; table->selected_y = i / table->columns; @@ -563,8 +565,41 @@ static void TXT_TableMousePress(TXT_UNCAST_ARG(table), int x, int y, int b) } } +// Determine whether the table is selectable. + +static int TXT_TableSelectable(TXT_UNCAST_ARG(table)) +{ + TXT_CAST_ARG(txt_table_t, table); + int i; + + // Is the currently-selected cell selectable? + + if (SelectableCell(table, table->selected_x, table->selected_y)) + { + return 1; + } + + // Find the first selectable cell and set selected_x, selected_y. + + for (i = 0; i < table->num_widgets; ++i) + { + if (table->widgets[i] != NULL + && TXT_SelectableWidget(table->widgets[i])) + { + table->selected_x = i % table->columns; + table->selected_y = i / table->columns; + return 1; + } + } + + // No selectable widgets exist within the table. + + return 0; +} + txt_widget_class_t txt_table_class = { + TXT_TableSelectable, TXT_CalcTableSize, TXT_TableDrawer, TXT_TableKeyPress, diff --git a/textscreen/txt_widget.c b/textscreen/txt_widget.c index 2300b32c..760943d5 100644 --- a/textscreen/txt_widget.c +++ b/textscreen/txt_widget.c @@ -83,9 +83,8 @@ void TXT_InitWidget(TXT_UNCAST_ARG(widget), txt_widget_class_t *widget_class) widget->widget_class = widget_class; widget->callback_table = TXT_NewCallbackTable(); - // Default values: visible and selectable + // Visible by default. - widget->selectable = 1; widget->visible = 1; // Align left by default @@ -214,3 +213,27 @@ void TXT_LayoutWidget(TXT_UNCAST_ARG(widget)) } } +int TXT_AlwaysSelectable(TXT_UNCAST_ARG(widget)) +{ + return 1; +} + +int TXT_NeverSelectable(TXT_UNCAST_ARG(widget)) +{ + return 0; +} + +int TXT_SelectableWidget(TXT_UNCAST_ARG(widget)) +{ + TXT_CAST_ARG(txt_widget_t, widget); + + if (widget->widget_class->selectable != NULL) + { + return widget->widget_class->selectable(widget); + } + else + { + return 0; + } +} + diff --git a/textscreen/txt_widget.h b/textscreen/txt_widget.h index 9688829d..bb895f92 100644 --- a/textscreen/txt_widget.h +++ b/textscreen/txt_widget.h @@ -77,9 +77,11 @@ typedef int (*TxtWidgetKeyPress)(TXT_UNCAST_ARG(widget), int key); typedef void (*TxtWidgetSignalFunc)(TXT_UNCAST_ARG(widget), void *user_data); typedef void (*TxtMousePressFunc)(TXT_UNCAST_ARG(widget), int x, int y, int b); typedef void (*TxtWidgetLayoutFunc)(TXT_UNCAST_ARG(widget)); +typedef int (*TxtWidgetSelectableFunc)(TXT_UNCAST_ARG(widget)); struct txt_widget_class_s { + TxtWidgetSelectableFunc selectable; TxtWidgetSizeCalc size_calc; TxtWidgetDrawer drawer; TxtWidgetKeyPress key_press; @@ -92,7 +94,6 @@ struct txt_widget_s { txt_widget_class_t *widget_class; txt_callback_table_t *callback_table; - int selectable; int visible; txt_horiz_align_t align; @@ -111,6 +112,8 @@ int TXT_WidgetKeyPress(TXT_UNCAST_ARG(widget), int key); void TXT_WidgetMousePress(TXT_UNCAST_ARG(widget), int x, int y, int b); void TXT_DestroyWidget(TXT_UNCAST_ARG(widget)); void TXT_LayoutWidget(TXT_UNCAST_ARG(widget)); +int TXT_AlwaysSelectable(TXT_UNCAST_ARG(widget)); +int TXT_NeverSelectable(TXT_UNCAST_ARG(widget)); /** * Set a callback function to be invoked when a signal occurs. @@ -134,6 +137,15 @@ void TXT_SignalConnect(TXT_UNCAST_ARG(widget), const char *signal_name, void TXT_SetWidgetAlign(TXT_UNCAST_ARG(widget), txt_horiz_align_t horiz_align); +/** + * Query whether a widget is selectable with the cursor. + * + * @param widget The widget. + * @return Non-zero if the widget is selectable. + */ + +int TXT_SelectableWidget(TXT_UNCAST_ARG(widget)); + #endif /* #ifndef TXT_WIDGET_H */ diff --git a/textscreen/txt_window.c b/textscreen/txt_window.c index 33f53d4a..46e71d3a 100644 --- a/textscreen/txt_window.c +++ b/textscreen/txt_window.c @@ -140,6 +140,15 @@ static void CalcWindowPosition(txt_window_t *window) static void LayoutActionArea(txt_window_t *window) { txt_widget_t *widget; + int space_available; + int space_left_offset; + + // We need to calculate the available horizontal space for the center + // action widget, so that we can center it within it. + // To start with, we have the entire action area available. + + space_available = window->window_w; + space_left_offset = 0; // Left action @@ -151,29 +160,43 @@ static void LayoutActionArea(txt_window_t *window) widget->x = window->window_x + 2; widget->y = window->window_y + window->window_h - widget->h - 1; + + // Adjust available space: + + space_available -= widget->w; + space_left_offset += widget->w; } - // Draw the center action + // Draw the right action - if (window->actions[TXT_HORIZ_CENTER] != NULL) + if (window->actions[TXT_HORIZ_RIGHT] != NULL) { - widget = (txt_widget_t *) window->actions[TXT_HORIZ_CENTER]; + widget = (txt_widget_t *) window->actions[TXT_HORIZ_RIGHT]; TXT_CalcWidgetSize(widget); - widget->x = window->window_x + (window->window_w - widget->w - 2) / 2; + widget->x = window->window_x + window->window_w - 2 - widget->w; widget->y = window->window_y + window->window_h - widget->h - 1; + + // Adjust available space: + + space_available -= widget->w; } - // Draw the right action + // Draw the center action - if (window->actions[TXT_HORIZ_RIGHT] != NULL) + if (window->actions[TXT_HORIZ_CENTER] != NULL) { - widget = (txt_widget_t *) window->actions[TXT_HORIZ_RIGHT]; + widget = (txt_widget_t *) window->actions[TXT_HORIZ_CENTER]; TXT_CalcWidgetSize(widget); - widget->x = window->window_x + window->window_w - 2 - widget->w; + // The left and right widgets have left a space sandwiched between + // them. Center this widget within that space. + + widget->x = window->window_x + + space_left_offset + + (space_available - widget->w) / 2; widget->y = window->window_y + window->window_h - widget->h - 1; } } diff --git a/textscreen/txt_window_action.c b/textscreen/txt_window_action.c index a326a5ed..e593b7b6 100644 --- a/textscreen/txt_window_action.c +++ b/textscreen/txt_window_action.c @@ -93,6 +93,7 @@ static void TXT_WindowActionMousePress(TXT_UNCAST_ARG(action), txt_widget_class_t txt_window_action_class = { + TXT_AlwaysSelectable, TXT_WindowActionSizeCalc, TXT_WindowActionDrawer, TXT_WindowActionKeyPress, |