foxygit / doom Log in
commit dd52766c7be893a6a5db31bc396ccef8d758af45
Author:     Simon Howard <fraggle@gmail.com>
AuthorDate: Sun Oct 23 19:25:55 2011 +0000
Commit:     Simon Howard <fraggle@gmail.com>
CommitDate: Sun Oct 23 19:25:55 2011 +0000

    Rework textscreen focus handling so that input boxes will stop editing
    when they lose their focus (thanks Twelve).

    Subversion-branch: /trunk/chocolate-doom
    Subversion-revision: 2460
---
 setup/txt_joybinput.c          |   4 +-
 setup/txt_keyinput.c           |   4 +-
 setup/txt_mouseinput.c         |   4 +-
 textscreen/txt_button.c        |   4 +-
 textscreen/txt_checkbox.c      |   4 +-
 textscreen/txt_desktop.c       |  26 +++++-
 textscreen/txt_desktop.h       |   3 +-
 textscreen/txt_dropdown.c      |   4 +-
 textscreen/txt_inputbox.c      | 184 ++++++++++++++++++++++-------------------
 textscreen/txt_label.c         |   2 +-
 textscreen/txt_radiobutton.c   |   4 +-
 textscreen/txt_scrollpane.c    |  18 +++-
 textscreen/txt_separator.c     |   2 +-
 textscreen/txt_spinctrl.c      |  48 +++++++----
 textscreen/txt_strut.c         |   2 +-
 textscreen/txt_table.c         | 113 ++++++++++++++++++-------
 textscreen/txt_widget.c        |  32 ++++++-
 textscreen/txt_widget.h        |  10 ++-
 textscreen/txt_window.c        |  25 +++++-
 textscreen/txt_window_action.c |   2 +-
 20 files changed, 330 insertions(+), 165 deletions(-)

diff --git a/setup/txt_joybinput.c b/setup/txt_joybinput.c
index 9ad26a45..fb4015b6 100644
--- a/setup/txt_joybinput.c
+++ b/setup/txt_joybinput.c
@@ -147,7 +147,7 @@ static void GetJoystickButtonDescription(int button, char *buf)
     sprintf(buf, "BUTTON #%i", button + 1);
 }

-static void TXT_JoystickInputDrawer(TXT_UNCAST_ARG(joystick_input), int selected)
+static void TXT_JoystickInputDrawer(TXT_UNCAST_ARG(joystick_input))
 {
     TXT_CAST_ARG(txt_joystick_input_t, joystick_input);
     char buf[20];
@@ -162,7 +162,7 @@ static void TXT_JoystickInputDrawer(TXT_UNCAST_ARG(joystick_input), int selected
         GetJoystickButtonDescription(*joystick_input->variable, buf);
     }

-    TXT_SetWidgetBG(joystick_input, selected);
+    TXT_SetWidgetBG(joystick_input);
     TXT_FGColor(TXT_COLOR_BRIGHT_WHITE);

     TXT_DrawString(buf);
diff --git a/setup/txt_keyinput.c b/setup/txt_keyinput.c
index 6f1ee4dd..19b0d56e 100644
--- a/setup/txt_keyinput.c
+++ b/setup/txt_keyinput.c
@@ -112,7 +112,7 @@ static void TXT_KeyInputSizeCalc(TXT_UNCAST_ARG(key_input))
 }


-static void TXT_KeyInputDrawer(TXT_UNCAST_ARG(key_input), int selected)
+static void TXT_KeyInputDrawer(TXT_UNCAST_ARG(key_input))
 {
     TXT_CAST_ARG(txt_key_input_t, key_input);
     char buf[20];
@@ -127,7 +127,7 @@ static void TXT_KeyInputDrawer(TXT_UNCAST_ARG(key_input), int selected)
         TXT_GetKeyDescription(*key_input->variable, buf);
     }

-    TXT_SetWidgetBG(key_input, selected);
+    TXT_SetWidgetBG(key_input);
     TXT_FGColor(TXT_COLOR_BRIGHT_WHITE);

     TXT_DrawString(buf);
diff --git a/setup/txt_mouseinput.c b/setup/txt_mouseinput.c
index c3e17299..30102b45 100644
--- a/setup/txt_mouseinput.c
+++ b/setup/txt_mouseinput.c
@@ -104,7 +104,7 @@ static void GetMouseButtonDescription(int button, char *buf)
     }
 }

-static void TXT_MouseInputDrawer(TXT_UNCAST_ARG(mouse_input), int selected)
+static void TXT_MouseInputDrawer(TXT_UNCAST_ARG(mouse_input))
 {
     TXT_CAST_ARG(txt_mouse_input_t, mouse_input);
     char buf[20];
@@ -119,7 +119,7 @@ static void TXT_MouseInputDrawer(TXT_UNCAST_ARG(mouse_input), int selected)
         GetMouseButtonDescription(*mouse_input->variable, buf);
     }

-    TXT_SetWidgetBG(mouse_input, selected);
+    TXT_SetWidgetBG(mouse_input);
     TXT_FGColor(TXT_COLOR_BRIGHT_WHITE);

     TXT_DrawString(buf);
diff --git a/textscreen/txt_button.c b/textscreen/txt_button.c
index 536e5f56..989bd4fc 100644
--- a/textscreen/txt_button.c
+++ b/textscreen/txt_button.c
@@ -38,7 +38,7 @@ static void TXT_ButtonSizeCalc(TXT_UNCAST_ARG(button))
     button->widget.h = 1;
 }

-static void TXT_ButtonDrawer(TXT_UNCAST_ARG(button), int selected)
+static void TXT_ButtonDrawer(TXT_UNCAST_ARG(button))
 {
     TXT_CAST_ARG(txt_button_t, button);
     int i;
@@ -48,7 +48,7 @@ static void TXT_ButtonDrawer(TXT_UNCAST_ARG(button), int selected)

     TXT_FGColor(TXT_COLOR_BRIGHT_WHITE);

-    TXT_SetWidgetBG(button, selected);
+    TXT_SetWidgetBG(button);

     TXT_DrawString(button->label);

diff --git a/textscreen/txt_checkbox.c b/textscreen/txt_checkbox.c
index f2183b7c..8c3271cf 100644
--- a/textscreen/txt_checkbox.c
+++ b/textscreen/txt_checkbox.c
@@ -40,7 +40,7 @@ static void TXT_CheckBoxSizeCalc(TXT_UNCAST_ARG(checkbox))
     checkbox->widget.h = 1;
 }

-static void TXT_CheckBoxDrawer(TXT_UNCAST_ARG(checkbox), int selected)
+static void TXT_CheckBoxDrawer(TXT_UNCAST_ARG(checkbox))
 {
     TXT_CAST_ARG(txt_checkbox_t, checkbox);
     int i;
@@ -67,7 +67,7 @@ static void TXT_CheckBoxDrawer(TXT_UNCAST_ARG(checkbox), int selected)

     TXT_DrawString(") ");

-    TXT_SetWidgetBG(checkbox, selected);
+    TXT_SetWidgetBG(checkbox);
     TXT_FGColor(TXT_COLOR_BRIGHT_WHITE);

     TXT_DrawString(checkbox->label);
diff --git a/textscreen/txt_desktop.c b/textscreen/txt_desktop.c
index c497f0e3..2c7caa27 100644
--- a/textscreen/txt_desktop.c
+++ b/textscreen/txt_desktop.c
@@ -41,14 +41,29 @@ static int main_loop_running = 0;

 void TXT_AddDesktopWindow(txt_window_t *win)
 {
+    // Previously-top window loses focus:
+
+    if (num_windows > 0)
+    {
+        TXT_SetWindowFocus(all_windows[num_windows - 1], 0);
+    }
+
     all_windows[num_windows] = win;
     ++num_windows;
+
+    // New window gains focus:
+
+    TXT_SetWindowFocus(win, 1);
 }

 void TXT_RemoveDesktopWindow(txt_window_t *win)
 {
     int from, to;

+    // Window must lose focus if it's being removed:
+
+    TXT_SetWindowFocus(win, 0);
+
     for (from=0, to=0; from<num_windows; ++from)
     {
         if (all_windows[from] != win)
@@ -57,8 +72,15 @@ void TXT_RemoveDesktopWindow(txt_window_t *win)
             ++to;
         }
     }
-
+
     num_windows = to;
+
+    // Top window gains focus:
+
+    if (num_windows > 0)
+    {
+        TXT_SetWindowFocus(all_windows[num_windows - 1], 1);
+    }
 }

 txt_window_t *TXT_GetActiveWindow(void)
@@ -140,7 +162,7 @@ void TXT_DrawDesktop(void)

     for (i=0; i<num_windows; ++i)
     {
-        TXT_DrawWindow(all_windows[i], i == num_windows - 1);
+        TXT_DrawWindow(all_windows[i]);
     }

     TXT_UpdateScreen();
diff --git a/textscreen/txt_desktop.h b/textscreen/txt_desktop.h
index 42a011e7..8d4726e5 100644
--- a/textscreen/txt_desktop.h
+++ b/textscreen/txt_desktop.h
@@ -34,7 +34,8 @@ void TXT_AddDesktopWindow(txt_window_t *win);
 void TXT_RemoveDesktopWindow(txt_window_t *win);
 void TXT_DrawDesktop(void);
 void TXT_DispatchEvents(void);
-void TXT_DrawWindow(txt_window_t *window, int selected);
+void TXT_DrawWindow(txt_window_t *window);
+void TXT_SetWindowFocus(txt_window_t *window, int focused);
 void TXT_WindowKeyPress(txt_window_t *window, int c);

 /**
diff --git a/textscreen/txt_dropdown.c b/textscreen/txt_dropdown.c
index c9a5d015..0856fb4e 100644
--- a/textscreen/txt_dropdown.c
+++ b/textscreen/txt_dropdown.c
@@ -206,7 +206,7 @@ static void TXT_DropdownListSizeCalc(TXT_UNCAST_ARG(list))
     list->widget.h = 1;
 }

-static void TXT_DropdownListDrawer(TXT_UNCAST_ARG(list), int selected)
+static void TXT_DropdownListDrawer(TXT_UNCAST_ARG(list))
 {
     TXT_CAST_ARG(txt_dropdown_list_t, list);
     unsigned int i;
@@ -214,7 +214,7 @@ static void TXT_DropdownListDrawer(TXT_UNCAST_ARG(list), int selected)

     // Set bg/fg text colors.

-    TXT_SetWidgetBG(list, selected);
+    TXT_SetWidgetBG(list);
     TXT_FGColor(TXT_COLOR_BRIGHT_WHITE);

     // Select a string to draw from the list, if the current value is
diff --git a/textscreen/txt_inputbox.c b/textscreen/txt_inputbox.c
index a28c342c..7056c497 100644
--- a/textscreen/txt_inputbox.c
+++ b/textscreen/txt_inputbox.c
@@ -32,7 +32,71 @@
 #include "txt_main.h"
 #include "txt_window.h"

-static void SetBufferFromValue(txt_inputbox_t *inputbox);
+extern txt_widget_class_t txt_inputbox_class;
+extern txt_widget_class_t txt_int_inputbox_class;
+
+static void SetBufferFromValue(txt_inputbox_t *inputbox)
+{
+    if (inputbox->widget.widget_class == &txt_inputbox_class)
+    {
+        char **value = (char **) inputbox->value;
+
+        if (*value != NULL)
+        {
+            strncpy(inputbox->buffer, *value, inputbox->size);
+            inputbox->buffer[inputbox->size] = '\0';
+        }
+        else
+        {
+            strcpy(inputbox->buffer, "");
+        }
+    }
+    else if (inputbox->widget.widget_class == &txt_int_inputbox_class)
+    {
+        int *value = (int *) inputbox->value;
+        sprintf(inputbox->buffer, "%i", *value);
+    }
+}
+
+static void StartEditing(txt_inputbox_t *inputbox)
+{
+    // Integer input boxes start from an empty buffer:
+
+    if (inputbox->widget.widget_class == &txt_int_inputbox_class)
+    {
+        strcpy(inputbox->buffer, "");
+    }
+    else
+    {
+        SetBufferFromValue(inputbox);
+    }
+
+    inputbox->editing = 1;
+}
+
+static void FinishEditing(txt_inputbox_t *inputbox)
+{
+    if (!inputbox->editing)
+    {
+        return;
+    }
+
+    // Save the new value back to the variable.
+
+    if (inputbox->widget.widget_class == &txt_inputbox_class)
+    {
+        free(*((char **)inputbox->value));
+        *((char **) inputbox->value) = strdup(inputbox->buffer);
+    }
+    else if (inputbox->widget.widget_class == &txt_int_inputbox_class)
+    {
+        *((int *) inputbox->value) = atoi(inputbox->buffer);
+    }
+
+    TXT_EmitSignal(&inputbox->widget, "changed");
+
+    inputbox->editing = 0;
+}

 static void TXT_InputBoxSizeCalc(TXT_UNCAST_ARG(inputbox))
 {
@@ -44,25 +108,27 @@ static void TXT_InputBoxSizeCalc(TXT_UNCAST_ARG(inputbox))
     inputbox->widget.h = 1;
 }

-static void TXT_InputBoxDrawer(TXT_UNCAST_ARG(inputbox), int selected)
+static void TXT_InputBoxDrawer(TXT_UNCAST_ARG(inputbox))
 {
     TXT_CAST_ARG(txt_inputbox_t, inputbox);
+    int focused;
     int i;
     int chars;
     int w;

+    focused = inputbox->widget.focused;
     w = inputbox->widget.w;

     // Select the background color based on whether we are currently
-    // editing, and if not, whether the widget is selected.
+    // editing, and if not, whether the widget is focused.

-    if (inputbox->editing && selected)
+    if (inputbox->editing && focused)
     {
         TXT_BGColor(TXT_COLOR_BLACK, 0);
     }
     else
     {
-        TXT_SetWidgetBG(inputbox, selected);
+        TXT_SetWidgetBG(inputbox);
     }

     TXT_FGColor(TXT_COLOR_BRIGHT_WHITE);
@@ -70,21 +136,21 @@ static void TXT_InputBoxDrawer(TXT_UNCAST_ARG(inputbox), int selected)
     if (!inputbox->editing)
     {
         // If not editing, use the current value from inputbox->value.
-
+
         SetBufferFromValue(inputbox);
     }
-
+
     TXT_DrawString(inputbox->buffer);

     chars = strlen(inputbox->buffer);

-    if (chars < w && inputbox->editing && selected)
+    if (chars < w && inputbox->editing && focused)
     {
         TXT_BGColor(TXT_COLOR_BLACK, 1);
         TXT_DrawString("_");
         ++chars;
     }
-
+
     for (i=chars; i < w; ++i)
     {
         TXT_DrawString(" ");
@@ -95,6 +161,14 @@ static void TXT_InputBoxDestructor(TXT_UNCAST_ARG(inputbox))
 {
     TXT_CAST_ARG(txt_inputbox_t, inputbox);

+    // If we're still editing when the widget is destroyed, save the
+    // value first.
+
+    if (inputbox->editing)
+    {
+        FinishEditing(inputbox);
+    }
+
     free(inputbox->buffer);
 }

@@ -125,8 +199,7 @@ static int TXT_InputBoxKeyPress(TXT_UNCAST_ARG(inputbox), int key)
     {
         if (key == KEY_ENTER)
         {
-            SetBufferFromValue(inputbox);
-            inputbox->editing = 1;
+            StartEditing(inputbox);
             return 1;
         }

@@ -135,12 +208,7 @@ static int TXT_InputBoxKeyPress(TXT_UNCAST_ARG(inputbox), int key)

     if (key == KEY_ENTER)
     {
-        free(*((char **)inputbox->value));
-        *((char **) inputbox->value) = strdup(inputbox->buffer);
-
-        TXT_EmitSignal(&inputbox->widget, "changed");
-
-        inputbox->editing = 0;
+        FinishEditing(inputbox);
     }

     if (key == KEY_ESCAPE)
@@ -159,50 +227,7 @@ static int TXT_InputBoxKeyPress(TXT_UNCAST_ARG(inputbox), int key)
     {
         Backspace(inputbox);
     }
-
-    return 1;
-}
-
-static int TXT_IntInputBoxKeyPress(TXT_UNCAST_ARG(inputbox), int key)
-{
-    TXT_CAST_ARG(txt_inputbox_t, inputbox);
-
-    if (!inputbox->editing)
-    {
-        if (key == KEY_ENTER)
-        {
-            strcpy(inputbox->buffer, "");
-            inputbox->editing = 1;
-            return 1;
-        }
-
-        return 0;
-    }
-
-    if (key == KEY_ENTER)
-    {
-        *((int *) inputbox->value) = atoi(inputbox->buffer);
-
-        inputbox->editing = 0;
-    }
-
-    if (key == KEY_ESCAPE)
-    {
-        inputbox->editing = 0;
-    }

-    if (isdigit(key))
-    {
-        // Add character to the buffer
-
-        AddCharacter(inputbox, key);
-    }
-
-    if (key == KEY_BACKSPACE)
-    {
-        Backspace(inputbox);
-    }
-
     return 1;
 }

@@ -224,6 +249,18 @@ static void TXT_InputBoxMousePress(TXT_UNCAST_ARG(inputbox),
     }
 }

+static void TXT_InputBoxFocused(TXT_UNCAST_ARG(inputbox), int focused)
+{
+    TXT_CAST_ARG(txt_inputbox_t, inputbox);
+
+    // Stop editing when we lose focus.
+
+    if (inputbox->editing && !focused)
+    {
+        FinishEditing(inputbox);
+    }
+}
+
 txt_widget_class_t txt_inputbox_class =
 {
     TXT_AlwaysSelectable,
@@ -233,6 +270,7 @@ txt_widget_class_t txt_inputbox_class =
     TXT_InputBoxDestructor,
     TXT_InputBoxMousePress,
     NULL,
+    TXT_InputBoxFocused,
 };

 txt_widget_class_t txt_int_inputbox_class =
@@ -240,35 +278,13 @@ txt_widget_class_t txt_int_inputbox_class =
     TXT_AlwaysSelectable,
     TXT_InputBoxSizeCalc,
     TXT_InputBoxDrawer,
-    TXT_IntInputBoxKeyPress,
+    TXT_InputBoxKeyPress,
     TXT_InputBoxDestructor,
     TXT_InputBoxMousePress,
     NULL,
+    TXT_InputBoxFocused,
 };

-static void SetBufferFromValue(txt_inputbox_t *inputbox)
-{
-    if (inputbox->widget.widget_class == &txt_inputbox_class)
-    {
-        char **value = (char **) inputbox->value;
-
-        if (*value != NULL)
-        {
-            strncpy(inputbox->buffer, *value, inputbox->size);
-            inputbox->buffer[inputbox->size] = '\0';
-        }
-        else
-        {
-            strcpy(inputbox->buffer, "");
-        }
-    }
-    else if (inputbox->widget.widget_class == &txt_int_inputbox_class)
-    {
-        int *value = (int *) inputbox->value;
-        sprintf(inputbox->buffer, "%i", *value);
-    }
-}
-
 txt_inputbox_t *TXT_NewInputBox(char **value, int size)
 {
     txt_inputbox_t *inputbox;
diff --git a/textscreen/txt_label.c b/textscreen/txt_label.c
index c973bce2..a2afc13b 100644
--- a/textscreen/txt_label.c
+++ b/textscreen/txt_label.c
@@ -36,7 +36,7 @@ static void TXT_LabelSizeCalc(TXT_UNCAST_ARG(label))
     label->widget.h = label->h;
 }

-static void TXT_LabelDrawer(TXT_UNCAST_ARG(label), int selected)
+static void TXT_LabelDrawer(TXT_UNCAST_ARG(label))
 {
     TXT_CAST_ARG(txt_label_t, label);
     unsigned int x, y;
diff --git a/textscreen/txt_radiobutton.c b/textscreen/txt_radiobutton.c
index 0755562d..10f94ad3 100644
--- a/textscreen/txt_radiobutton.c
+++ b/textscreen/txt_radiobutton.c
@@ -40,7 +40,7 @@ static void TXT_RadioButtonSizeCalc(TXT_UNCAST_ARG(radiobutton))
     radiobutton->widget.h = 1;
 }

-static void TXT_RadioButtonDrawer(TXT_UNCAST_ARG(radiobutton), int selected)
+static void TXT_RadioButtonDrawer(TXT_UNCAST_ARG(radiobutton))
 {
     TXT_CAST_ARG(txt_radiobutton_t, radiobutton);
     int i;
@@ -67,7 +67,7 @@ static void TXT_RadioButtonDrawer(TXT_UNCAST_ARG(radiobutton), int selected)

     TXT_DrawString(") ");

-    TXT_SetWidgetBG(radiobutton, selected);
+    TXT_SetWidgetBG(radiobutton);
     TXT_FGColor(TXT_COLOR_BRIGHT_WHITE);

     TXT_DrawString(radiobutton->label);
diff --git a/textscreen/txt_scrollpane.c b/textscreen/txt_scrollpane.c
index 2fd45c55..4f468ffe 100644
--- a/textscreen/txt_scrollpane.c
+++ b/textscreen/txt_scrollpane.c
@@ -160,7 +160,7 @@ static void TXT_ScrollPaneSizeCalc(TXT_UNCAST_ARG(scrollpane))
     }
 }

-static void TXT_ScrollPaneDrawer(TXT_UNCAST_ARG(scrollpane), int selected)
+static void TXT_ScrollPaneDrawer(TXT_UNCAST_ARG(scrollpane))
 {
     TXT_CAST_ARG(txt_scrollpane_t, scrollpane);
     int x1, y1, x2, y2;
@@ -199,7 +199,7 @@ static void TXT_ScrollPaneDrawer(TXT_UNCAST_ARG(scrollpane), int selected)

     if (scrollpane->child != NULL)
     {
-        TXT_DrawWidget(scrollpane->child, selected);
+        TXT_DrawWidget(scrollpane->child);
     }

     // Restore old clipping area.
@@ -217,6 +217,19 @@ static void TXT_ScrollPaneDestructor(TXT_UNCAST_ARG(scrollpane))
     }
 }

+static void TXT_ScrollPaneFocused(TXT_UNCAST_ARG(scrollpane), int focused)
+{
+    TXT_CAST_ARG(txt_scrollpane_t, scrollpane);
+
+    // Whether the child is focused depends only on whether the scroll pane
+    // itself is focused. Pass through focus to the child.
+
+    if (scrollpane->child != NULL)
+    {
+        TXT_SetWidgetFocus(scrollpane->child, focused);
+    }
+}
+
 // Hack for tables - when browsing a table inside a scroll pane,
 // automatically scroll the window to show the newly-selected
 // item.
@@ -540,6 +553,7 @@ txt_widget_class_t txt_scrollpane_class =
     TXT_ScrollPaneDestructor,
     TXT_ScrollPaneMousePress,
     TXT_ScrollPaneLayout,
+    TXT_ScrollPaneFocused,
 };

 txt_scrollpane_t *TXT_NewScrollPane(int w, int h, TXT_UNCAST_ARG(target))
diff --git a/textscreen/txt_separator.c b/textscreen/txt_separator.c
index 89db0b03..ce6e29ec 100644
--- a/textscreen/txt_separator.c
+++ b/textscreen/txt_separator.c
@@ -46,7 +46,7 @@ static void TXT_SeparatorSizeCalc(TXT_UNCAST_ARG(separator))
     separator->widget.h = 1;
 }

-static void TXT_SeparatorDrawer(TXT_UNCAST_ARG(separator), int selected)
+static void TXT_SeparatorDrawer(TXT_UNCAST_ARG(separator))
 {
     TXT_CAST_ARG(txt_separator_t, separator);
     int x, y;
diff --git a/textscreen/txt_spinctrl.c b/textscreen/txt_spinctrl.c
index 2b2d4d09..a4d20343 100644
--- a/textscreen/txt_spinctrl.c
+++ b/textscreen/txt_spinctrl.c
@@ -142,11 +142,14 @@ static void SetBuffer(txt_spincontrol_t *spincontrol)
     }
 }

-static void TXT_SpinControlDrawer(TXT_UNCAST_ARG(spincontrol), int selected)
+static void TXT_SpinControlDrawer(TXT_UNCAST_ARG(spincontrol))
 {
     TXT_CAST_ARG(txt_spincontrol_t, spincontrol);
     unsigned int i;
     unsigned int padding;
+    int focused;
+
+    focused = spincontrol->widget.focused;

     TXT_FGColor(TXT_COLOR_BRIGHT_CYAN);
     TXT_BGColor(TXT_WINDOW_BACKGROUND, 0);
@@ -157,13 +160,13 @@ static void TXT_SpinControlDrawer(TXT_UNCAST_ARG(spincontrol), int selected)

     // Choose background color

-    if (selected && spincontrol->editing)
+    if (focused && spincontrol->editing)
     {
         TXT_BGColor(TXT_COLOR_BLACK, 0);
     }
     else
     {
-        TXT_SetWidgetBG(spincontrol, selected);
+        TXT_SetWidgetBG(spincontrol);
     }

     if (!spincontrol->editing)
@@ -239,6 +242,23 @@ static void EnforceLimits(txt_spincontrol_t *spincontrol)
     }
 }

+static void FinishEditing(txt_spincontrol_t *spincontrol)
+{
+    switch (spincontrol->type)
+    {
+        case TXT_SPINCONTROL_INT:
+            spincontrol->value->i = atoi(spincontrol->buffer);
+            break;
+
+        case TXT_SPINCONTROL_FLOAT:
+            spincontrol->value->f = (float) atof(spincontrol->buffer);
+            break;
+    }
+
+    spincontrol->editing = 0;
+    EnforceLimits(spincontrol);
+}
+
 static int TXT_SpinControlKeyPress(TXT_UNCAST_ARG(spincontrol), int key)
 {
     TXT_CAST_ARG(txt_spincontrol_t, spincontrol);
@@ -249,19 +269,7 @@ static int TXT_SpinControlKeyPress(TXT_UNCAST_ARG(spincontrol), int key)
     {
         if (key == KEY_ENTER)
         {
-            switch (spincontrol->type)
-            {
-                case TXT_SPINCONTROL_INT:
-                    spincontrol->value->i = atoi(spincontrol->buffer);
-                    break;
-
-                case TXT_SPINCONTROL_FLOAT:
-                    spincontrol->value->f = (float) atof(spincontrol->buffer);
-                    break;
-            }
-
-            spincontrol->editing = 0;
-            EnforceLimits(spincontrol);
+            FinishEditing(spincontrol);
             return 1;
         }

@@ -352,6 +360,13 @@ static void TXT_SpinControlMousePress(TXT_UNCAST_ARG(spincontrol),
     }
 }

+static void TXT_SpinControlFocused(TXT_UNCAST_ARG(spincontrol), int focused)
+{
+    TXT_CAST_ARG(txt_spincontrol_t, spincontrol);
+
+    FinishEditing(spincontrol);
+}
+
 txt_widget_class_t txt_spincontrol_class =
 {
     TXT_AlwaysSelectable,
@@ -361,6 +376,7 @@ txt_widget_class_t txt_spincontrol_class =
     TXT_SpinControlDestructor,
     TXT_SpinControlMousePress,
     NULL,
+    TXT_SpinControlFocused,
 };

 static txt_spincontrol_t *TXT_BaseSpinControl(void)
diff --git a/textscreen/txt_strut.c b/textscreen/txt_strut.c
index f3a618f3..367e798a 100644
--- a/textscreen/txt_strut.c
+++ b/textscreen/txt_strut.c
@@ -39,7 +39,7 @@ static void TXT_StrutSizeCalc(TXT_UNCAST_ARG(strut))
     strut->widget.h = strut->height;
 }

-static void TXT_StrutDrawer(TXT_UNCAST_ARG(strut), int selected)
+static void TXT_StrutDrawer(TXT_UNCAST_ARG(strut))
 {
     // Nothing is drawn for a strut.
 }
diff --git a/textscreen/txt_table.c b/textscreen/txt_table.c
index 7a8624f0..573df428 100644
--- a/textscreen/txt_table.c
+++ b/textscreen/txt_table.c
@@ -264,6 +264,48 @@ static int FindSelectableColumn(txt_table_t *table, int row, int start_col)
     return -1;
 }

+// Change the selected widget.
+
+static void ChangeSelection(txt_table_t *table, int x, int y)
+{
+    txt_widget_t *cur_widget;
+    txt_widget_t *new_widget;
+    int i;
+
+    // No change?
+
+    if (x == table->selected_x && y == table->selected_y)
+    {
+        return;
+    }
+
+    // Unfocus current widget:
+
+    i = table->selected_y * table->columns + table->selected_x;
+
+    if (i < table->num_widgets)
+    {
+        cur_widget = table->widgets[i];
+
+        if (table->widget.focused && cur_widget != NULL)
+        {
+            TXT_SetWidgetFocus(cur_widget, 0);
+        }
+    }
+
+    // Focus new widget.
+
+    new_widget = table->widgets[y * table->columns + x];
+
+    table->selected_x = x;
+    table->selected_y = y;
+
+    if (table->widget.focused && new_widget != NULL)
+    {
+        TXT_SetWidgetFocus(new_widget, 1);
+    }
+}
+
 static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key)
 {
     TXT_CAST_ARG(txt_table_t, table);
@@ -300,8 +342,7 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key)
             {
                 // Found a selectable widget in this column!

-                table->selected_x = new_x;
-                table->selected_y = new_y;
+                ChangeSelection(table, new_x, new_y);

                 return 1;
             }
@@ -317,13 +358,12 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key)
         for (new_y = table->selected_y - 1; new_y >= 0; --new_y)
         {
             new_x = FindSelectableColumn(table, new_y, table->selected_x);
-
+
             if (new_x >= 0)
             {
                 // Found a selectable widget in this column!

-                table->selected_x = new_x;
-                table->selected_y = new_y;
+                ChangeSelection(table, new_x, new_y);

                 return 1;
             }
@@ -342,7 +382,7 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key)
             {
                 // Found a selectable widget!

-                table->selected_x = new_x;
+                ChangeSelection(table, new_x, table->selected_y);

                 return 1;
             }
@@ -361,7 +401,7 @@ static int TXT_TableKeyPress(TXT_UNCAST_ARG(table), int key)
             {
                 // Found a selectable widget!

-                table->selected_x = new_x;
+                ChangeSelection(table, new_x, table->selected_y);

                 return 1;
             }
@@ -388,8 +428,7 @@ static void CheckValidSelection(txt_table_t *table)
         {
             // Found a selectable column.

-            table->selected_x = new_x;
-            table->selected_y = new_y;
+            ChangeSelection(table, new_x, new_y);

             break;
         }
@@ -502,25 +541,20 @@ static void TXT_TableLayout(TXT_UNCAST_ARG(table))
     free(row_heights);
     free(column_widths);
 }
-
-static void TXT_TableDrawer(TXT_UNCAST_ARG(table), int selected)
+
+static void TXT_TableDrawer(TXT_UNCAST_ARG(table))
 {
     TXT_CAST_ARG(txt_table_t, table);
     txt_widget_t *widget;
-    int selected_cell;
     int i;
-
+
     // Check the table's current selection points at something valid before
     // drawing.

     CheckValidSelection(table);

-    // Find the index of the currently-selected widget.
-
-    selected_cell = table->selected_y * table->columns + table->selected_x;
-
     // Draw all cells
-
+
     for (i=0; i<table->num_widgets; ++i)
     {
         widget = table->widgets[i];
@@ -528,7 +562,7 @@ static void TXT_TableDrawer(TXT_UNCAST_ARG(table), int selected)
         if (widget != NULL)
         {
             TXT_GotoXY(widget->x, widget->y);
-            TXT_DrawWidget(widget, selected && i == selected_cell);
+            TXT_DrawWidget(widget);
         }
     }
 }
@@ -558,8 +592,8 @@ static void TXT_TableMousePress(TXT_UNCAST_ARG(table), int x, int y, int b)

                 if (TXT_SelectableWidget(widget))
                 {
-                    table->selected_x = i % table->columns;
-                    table->selected_y = i / table->columns;
+                    ChangeSelection(table, i % table->columns,
+                                           i / table->columns);
                 }

                 // Propagate click
@@ -593,8 +627,7 @@ static int TXT_TableSelectable(TXT_UNCAST_ARG(table))
         if (table->widgets[i] != NULL
          && TXT_SelectableWidget(table->widgets[i]))
         {
-            table->selected_x = i % table->columns;
-            table->selected_y = i / table->columns;
+            ChangeSelection(table, i % table->columns, i / table->columns);
             return 1;
         }
     }
@@ -604,6 +637,24 @@ static int TXT_TableSelectable(TXT_UNCAST_ARG(table))
     return 0;
 }

+// Need to pass through focus changes to the selected child widget.
+
+static void TXT_TableFocused(TXT_UNCAST_ARG(table), int focused)
+{
+    TXT_CAST_ARG(txt_table_t, table);
+    int i;
+
+    i = table->selected_y * table->columns + table->selected_x;
+
+    if (i < table->num_widgets)
+    {
+        if (table->widgets[i] != NULL)
+        {
+            TXT_SetWidgetFocus(table->widgets[i], focused);
+        }
+    }
+}
+
 txt_widget_class_t txt_table_class =
 {
     TXT_TableSelectable,
@@ -613,6 +664,7 @@ txt_widget_class_t txt_table_class =
     TXT_TableDestructor,
     TXT_TableMousePress,
     TXT_TableLayout,
+    TXT_TableFocused,
 };

 void TXT_InitTable(txt_table_t *table, int columns)
@@ -757,17 +809,16 @@ int TXT_SelectWidget(TXT_UNCAST_ARG(table), TXT_UNCAST_ARG(widget))
         {
             continue;
         }
-
+
         if (table->widgets[i] == widget)
         {
             // Found the item!  Select it and return.
-
-            table->selected_x = i % table->columns;
-            table->selected_y = i / table->columns;
+
+            ChangeSelection(table, i % table->columns, i / table->columns);

             return 1;
         }
-
+
         if (table->widgets[i]->widget_class == &txt_table_class)
         {
             // This item is a subtable.  Recursively search this table.
@@ -776,8 +827,7 @@ int TXT_SelectWidget(TXT_UNCAST_ARG(table), TXT_UNCAST_ARG(widget))
             {
                 // Found it in the subtable.  Select this subtable and return.

-                table->selected_x = i % table->columns;
-                table->selected_y = i / table->columns;
+                ChangeSelection(table, i % table->columns, i / table->columns);

                 return 1;
             }
@@ -874,8 +924,7 @@ int TXT_PageTable(TXT_UNCAST_ARG(table), int pagex, int pagey)
                 // Found a selectable widget in this column!
                 // Select it anyway in case we don't find something better.

-                table->selected_x = new_x;
-                table->selected_y = new_y;
+                ChangeSelection(table, new_x, new_y);
                 changed = 1;

                 // ...but is it far enough away?
diff --git a/textscreen/txt_widget.c b/textscreen/txt_widget.c
index d47a7507..7d31ad68 100644
--- a/textscreen/txt_widget.c
+++ b/textscreen/txt_widget.c
@@ -86,6 +86,10 @@ void TXT_InitWidget(TXT_UNCAST_ARG(widget), txt_widget_class_t *widget_class)
     widget->callback_table = TXT_NewCallbackTable();
     widget->parent = NULL;

+    // Not focused until we hear otherwise.
+
+    widget->focused = 0;
+
     // Visible by default.

     widget->visible = 1;
@@ -155,7 +159,7 @@ void TXT_CalcWidgetSize(TXT_UNCAST_ARG(widget))
     widget->widget_class->size_calc(widget);
 }

-void TXT_DrawWidget(TXT_UNCAST_ARG(widget), int selected)
+void TXT_DrawWidget(TXT_UNCAST_ARG(widget))
 {
     TXT_CAST_ARG(txt_widget_t, widget);

@@ -165,7 +169,7 @@ void TXT_DrawWidget(TXT_UNCAST_ARG(widget), int selected)

     // Call drawer method

-    widget->widget_class->drawer(widget, selected);
+    widget->widget_class->drawer(widget);
 }

 void TXT_DestroyWidget(TXT_UNCAST_ARG(widget))
@@ -189,6 +193,26 @@ int TXT_WidgetKeyPress(TXT_UNCAST_ARG(widget), int key)
     return 0;
 }

+void TXT_SetWidgetFocus(TXT_UNCAST_ARG(widget), int focused)
+{
+    TXT_CAST_ARG(txt_widget_t, widget);
+
+    if (widget == NULL)
+    {
+        return;
+    }
+
+    if (widget->focused != focused)
+    {
+        widget->focused = focused;
+
+        if (widget->widget_class->focus_change != NULL)
+        {
+            widget->widget_class->focus_change(widget, focused);
+        }
+    }
+}
+
 void TXT_SetWidgetAlign(TXT_UNCAST_ARG(widget), txt_horiz_align_t horiz_align)
 {
     TXT_CAST_ARG(txt_widget_t, widget);
@@ -281,11 +305,11 @@ int TXT_HoveringOverWidget(TXT_UNCAST_ARG(widget))
          && y >= widget->y && y < widget->y + widget->h);
 }

-void TXT_SetWidgetBG(TXT_UNCAST_ARG(widget), int selected)
+void TXT_SetWidgetBG(TXT_UNCAST_ARG(widget))
 {
     TXT_CAST_ARG(txt_widget_t, widget);

-    if (selected)
+    if (widget->focused)
     {
         TXT_BGColor(TXT_COLOR_GREY, 0);
     }
diff --git a/textscreen/txt_widget.h b/textscreen/txt_widget.h
index ebb5a292..f64b9517 100644
--- a/textscreen/txt_widget.h
+++ b/textscreen/txt_widget.h
@@ -71,13 +71,14 @@ typedef struct txt_widget_class_s txt_widget_class_t;
 typedef struct txt_callback_table_s txt_callback_table_t;

 typedef void (*TxtWidgetSizeCalc)(TXT_UNCAST_ARG(widget));
-typedef void (*TxtWidgetDrawer)(TXT_UNCAST_ARG(widget), int selected);
+typedef void (*TxtWidgetDrawer)(TXT_UNCAST_ARG(widget));
 typedef void (*TxtWidgetDestroy)(TXT_UNCAST_ARG(widget));
 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));
+typedef void (*TxtWidgetFocusFunc)(TXT_UNCAST_ARG(widget), int focused);

 struct txt_widget_class_s
 {
@@ -88,6 +89,7 @@ struct txt_widget_class_s
     TxtWidgetDestroy destructor;
     TxtMousePressFunc mouse_press;
     TxtWidgetLayoutFunc layout;
+    TxtWidgetFocusFunc focus_change;
 };

 struct txt_widget_s
@@ -96,6 +98,7 @@ struct txt_widget_s
     txt_callback_table_t *callback_table;
     int visible;
     txt_horiz_align_t align;
+    int focused;

     // These are set automatically when the window is drawn and should
     // not be set manually.
@@ -110,7 +113,7 @@ struct txt_widget_s

 void TXT_InitWidget(TXT_UNCAST_ARG(widget), txt_widget_class_t *widget_class);
 void TXT_CalcWidgetSize(TXT_UNCAST_ARG(widget));
-void TXT_DrawWidget(TXT_UNCAST_ARG(widget), int selected);
+void TXT_DrawWidget(TXT_UNCAST_ARG(widget));
 void TXT_EmitSignal(TXT_UNCAST_ARG(widget), const char *signal_name);
 int TXT_WidgetKeyPress(TXT_UNCAST_ARG(widget), int key);
 void TXT_WidgetMousePress(TXT_UNCAST_ARG(widget), int x, int y, int b);
@@ -118,6 +121,7 @@ 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));
+void TXT_SetWidgetFocus(TXT_UNCAST_ARG(widget), int focused);

 /**
  * Set a callback function to be invoked when a signal occurs.
@@ -167,7 +171,7 @@ int TXT_HoveringOverWidget(TXT_UNCAST_ARG(widget));
  * @param selected     Whether the widget is selected.
  */

-void TXT_SetWidgetBG(TXT_UNCAST_ARG(widget), int selected);
+void TXT_SetWidgetBG(TXT_UNCAST_ARG(widget));

 /**
  * Query whether the specified widget is contained within another
diff --git a/textscreen/txt_window.c b/textscreen/txt_window.c
index cd8a6c7a..62f3ac8a 100644
--- a/textscreen/txt_window.c
+++ b/textscreen/txt_window.c
@@ -216,7 +216,7 @@ static void DrawActionArea(txt_window_t *window)
     {
         if (window->actions[i] != NULL)
         {
-            TXT_DrawWidget(window->actions[i], 0);
+            TXT_DrawWidget(window->actions[i]);
         }
     }
 }
@@ -315,7 +315,7 @@ void TXT_LayoutWindow(txt_window_t *window)
     TXT_LayoutWidget(widgets);
 }

-void TXT_DrawWindow(txt_window_t *window, int selected)
+void TXT_DrawWindow(txt_window_t *window)
 {
     txt_widget_t *widgets;

@@ -329,7 +329,7 @@ void TXT_DrawWindow(txt_window_t *window, int selected)

     // Draw all widgets

-    TXT_DrawWidget(window, selected);
+    TXT_DrawWidget(window);

     // Draw an action area, if we have one

@@ -408,7 +408,21 @@ static void MouseButtonPress(txt_window_t *window, int b)
          && x >= widget->x && x < (signed) (widget->x + widget->w)
          && y >= widget->y && y < (signed) (widget->y + widget->h))
         {
+            int was_focused;
+
+            // Main table temporarily loses focus when action area button
+            // is clicked. This way, any active input boxes that depend
+            // on having focus will save their values before the
+            // action is performed.
+
+            was_focused = window->table.widget.focused;
+            TXT_SetWidgetFocus(window, 0);
+            TXT_SetWidgetFocus(window, was_focused);
+
+            // Pass through mouse press.
+
             TXT_WidgetMousePress(widget, x, y, b);
+
             break;
         }
     }
@@ -472,3 +486,8 @@ void TXT_SetMouseListener(txt_window_t *window,
     window->mouse_listener_data = user_data;
 }

+void TXT_SetWindowFocus(txt_window_t *window, int focused)
+{
+    TXT_SetWidgetFocus(window, focused);
+}
+
diff --git a/textscreen/txt_window_action.c b/textscreen/txt_window_action.c
index df0e4ea3..cf5ac4a7 100644
--- a/textscreen/txt_window_action.c
+++ b/textscreen/txt_window_action.c
@@ -44,7 +44,7 @@ static void TXT_WindowActionSizeCalc(TXT_UNCAST_ARG(action))
     action->widget.h = 1;
 }

-static void TXT_WindowActionDrawer(TXT_UNCAST_ARG(action), int selected)
+static void TXT_WindowActionDrawer(TXT_UNCAST_ARG(action))
 {
     TXT_CAST_ARG(txt_window_action_t, action);
     char buf[10];