ref: d1a3967194323b08227b20822acedb837e05281a
parent: 6a2d4763a9080cf88ca9f0b588b8187963eeacf5
parent: e225e0c93ce58bb0e33c174847305d39800fd755
author: Simon Howard <[email protected]>
date: Fri Dec 10 18:46:22 EST 2010
Merge from trunk. Subversion-branch: /branches/raven-branch Subversion-revision: 2214
--- a/NEWS
+++ b/NEWS
@@ -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):
--- a/NOT-BUGS
+++ b/NOT-BUGS
@@ -65,7 +65,7 @@
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
--- 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>
--- a/src/doom/d_net.c
+++ b/src/doom/d_net.c
@@ -204,8 +204,8 @@
G_BuildTiccmd(&cmd);
#ifdef FEATURE_MULTIPLAYER
-
- if (netgame && !demoplayback)
+
+ if (net_client_connected)
{
NET_CL_SendTiccmd(&cmd, maketic);
}
@@ -460,6 +460,19 @@
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,
@@ -467,6 +480,7 @@
{
net_addr_t *addr = NULL;
int i;
+
#ifdef FEATURE_MULTIPLAYER
--- a/src/doom/g_game.c
+++ b/src/doom/g_game.c
@@ -2145,16 +2145,11 @@
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
--- a/src/i_system.c
+++ b/src/i_system.c
@@ -327,9 +327,9 @@
// 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 @@
msgbuf, strlen(msgbuf) + 1,
wmsgbuf, sizeof(wmsgbuf));
- MessageBoxW(NULL, wmsgbuf, L"Error", MB_OK);
+ MessageBoxW(NULL, wmsgbuf, L"", MB_OK);
}
#endif
--- a/src/net_gui.c
+++ b/src/net_gui.c
@@ -287,7 +287,7 @@
if (!net_client_connected)
{
- I_Error("Disconnected from server");
+ I_Error("Lost connection to server");
}
TXT_Sleep(100);
--- 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.
--- a/src/net_server.c
+++ b/src/net_server.c
@@ -70,6 +70,11 @@
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 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 @@
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;
--- a/src/setup/mainmenu.c
+++ b/src/setup/mainmenu.c
@@ -189,6 +189,7 @@
{
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 @@
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);
}
--- a/src/setup/multiplayer.c
+++ b/src/setup/multiplayer.c
@@ -209,7 +209,11 @@
}
}
-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 @@
AddExtraParameters(exec);
AddIWADParameter(exec);
- AddCmdLineParameter(exec, "-server");
AddCmdLineParameter(exec, "-skill %i", skill + 1);
if (gamemission == hexen)
@@ -244,20 +247,6 @@
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,8 +257,28 @@
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);
TXT_Shutdown();
@@ -282,6 +291,16 @@
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,13 +563,28 @@
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 @@
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 @@
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 @@
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 @@
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,17 +683,39 @@
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)
--- 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);
--- a/src/setup/txt_joybinput.c
+++ b/src/setup/txt_joybinput.c
@@ -206,6 +206,7 @@
txt_widget_class_t txt_joystick_input_class =
{
+ TXT_AlwaysSelectable,
TXT_JoystickInputSizeCalc,
TXT_JoystickInputDrawer,
TXT_JoystickInputKeyPress,
--- a/src/setup/txt_keyinput.c
+++ b/src/setup/txt_keyinput.c
@@ -171,6 +171,7 @@
txt_widget_class_t txt_key_input_class =
{
+ TXT_AlwaysSelectable,
TXT_KeyInputSizeCalc,
TXT_KeyInputDrawer,
TXT_KeyInputKeyPress,
--- a/src/setup/txt_mouseinput.c
+++ b/src/setup/txt_mouseinput.c
@@ -164,6 +164,7 @@
txt_widget_class_t txt_mouse_input_class =
{
+ TXT_AlwaysSelectable,
TXT_MouseInputSizeCalc,
TXT_MouseInputDrawer,
TXT_MouseInputKeyPress,
--- a/textscreen/examples/guitest.c
+++ b/textscreen/examples/guitest.c
@@ -163,8 +163,8 @@
{
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 @@
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);
--- a/textscreen/txt_button.c
+++ b/textscreen/txt_button.c
@@ -96,6 +96,7 @@
txt_widget_class_t txt_button_class =
{
+ TXT_AlwaysSelectable,
TXT_ButtonSizeCalc,
TXT_ButtonDrawer,
TXT_ButtonKeyPress,
--- a/textscreen/txt_checkbox.c
+++ b/textscreen/txt_checkbox.c
@@ -117,6 +117,7 @@
txt_widget_class_t txt_checkbox_class =
{
+ TXT_AlwaysSelectable,
TXT_CheckBoxSizeCalc,
TXT_CheckBoxDrawer,
TXT_CheckBoxKeyPress,
--- a/textscreen/txt_dropdown.c
+++ b/textscreen/txt_dropdown.c
@@ -262,6 +262,7 @@
txt_widget_class_t txt_dropdown_list_class =
{
+ TXT_AlwaysSelectable,
TXT_DropdownListSizeCalc,
TXT_DropdownListDrawer,
TXT_DropdownListKeyPress,
--- a/textscreen/txt_inputbox.c
+++ b/textscreen/txt_inputbox.c
@@ -232,6 +232,7 @@
txt_widget_class_t txt_inputbox_class =
{
+ TXT_AlwaysSelectable,
TXT_InputBoxSizeCalc,
TXT_InputBoxDrawer,
TXT_InputBoxKeyPress,
@@ -242,6 +243,7 @@
txt_widget_class_t txt_int_inputbox_class =
{
+ TXT_AlwaysSelectable,
TXT_InputBoxSizeCalc,
TXT_InputBoxDrawer,
TXT_IntInputBoxKeyPress,
--- a/textscreen/txt_label.c
+++ b/textscreen/txt_label.c
@@ -104,6 +104,7 @@
txt_widget_class_t txt_label_class =
{
+ TXT_NeverSelectable,
TXT_LabelSizeCalc,
TXT_LabelDrawer,
NULL,
@@ -170,7 +171,6 @@
label = malloc(sizeof(txt_label_t));
TXT_InitWidget(label, &txt_label_class);
- label->widget.selectable = 0;
label->label = NULL;
label->lines = NULL;
--- a/textscreen/txt_radiobutton.c
+++ b/textscreen/txt_radiobutton.c
@@ -121,6 +121,7 @@
txt_widget_class_t txt_radiobutton_class =
{
+ TXT_AlwaysSelectable,
TXT_RadioButtonSizeCalc,
TXT_RadioButtonDrawer,
TXT_RadioButtonKeyPress,
--- a/textscreen/txt_scrollpane.c
+++ b/textscreen/txt_scrollpane.c
@@ -138,7 +138,7 @@
}
if (scrollpane->expand_h)
{
- scrollpane->h = FullWidth(scrollpane);
+ scrollpane->h = FullHeight(scrollpane);
}
scrollpane->widget.w = scrollpane->w;
@@ -486,8 +486,26 @@
}
}
+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,
--- a/textscreen/txt_separator.c
+++ b/textscreen/txt_separator.c
@@ -82,6 +82,7 @@
txt_widget_class_t txt_separator_class =
{
+ TXT_NeverSelectable,
TXT_SeparatorSizeCalc,
TXT_SeparatorDrawer,
NULL,
@@ -97,7 +98,6 @@
separator = malloc(sizeof(txt_separator_t));
TXT_InitWidget(separator, &txt_separator_class);
- separator->widget.selectable = 0;
if (label != NULL)
{
--- a/textscreen/txt_spinctrl.c
+++ b/textscreen/txt_spinctrl.c
@@ -358,6 +358,7 @@
txt_widget_class_t txt_spincontrol_class =
{
+ TXT_AlwaysSelectable,
TXT_SpinControlSizeCalc,
TXT_SpinControlDrawer,
TXT_SpinControlKeyPress,
--- a/textscreen/txt_strut.c
+++ b/textscreen/txt_strut.c
@@ -55,6 +55,7 @@
txt_widget_class_t txt_strut_class =
{
+ TXT_NeverSelectable,
TXT_StrutSizeCalc,
TXT_StrutDrawer,
TXT_StrutKeyPress,
@@ -70,7 +71,6 @@
strut = malloc(sizeof(txt_strut_t));
TXT_InitWidget(strut, &txt_strut_class);
- strut->widget.selectable = 0;
strut->width = width;
strut->height = height;
--- a/textscreen/txt_table.c
+++ b/textscreen/txt_table.c
@@ -202,7 +202,7 @@
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 @@
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,7 +239,7 @@
{
// Search to the right
- if (SelectableWidget(table, start_col + x, row))
+ if (SelectableCell(table, start_col + x, row))
{
return start_col + x;
}
@@ -244,7 +246,7 @@
// 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 @@
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 @@
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 @@
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 @@
// 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 @@
}
}
+// 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,
--- a/textscreen/txt_widget.c
+++ b/textscreen/txt_widget.c
@@ -83,9 +83,8 @@
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
@@ -211,6 +210,30 @@
if (widget->widget_class->layout != NULL)
{
widget->widget_class->layout(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;
}
}
--- a/textscreen/txt_widget.h
+++ b/textscreen/txt_widget.h
@@ -77,9 +77,11 @@
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 @@
{
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 @@
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.
@@ -133,6 +136,15 @@
*/
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 */
--- a/textscreen/txt_window.c
+++ b/textscreen/txt_window.c
@@ -140,7 +140,16 @@
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
if (window->actions[TXT_HORIZ_LEFT] != NULL)
@@ -151,29 +160,43 @@
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;
}
}
--- a/textscreen/txt_window_action.c
+++ b/textscreen/txt_window_action.c
@@ -93,6 +93,7 @@
txt_widget_class_t txt_window_action_class =
{
+ TXT_AlwaysSelectable,
TXT_WindowActionSizeCalc,
TXT_WindowActionDrawer,
TXT_WindowActionKeyPress,