shithub: choc

Download patch

ref: 47af28e16863f08efd7f8f8ab04b9bcf44846cbd
parent: 917aa5b184fdfc03a80ce46fb857c3a2780dc705
parent: bd0f386997cccbeb41103d76f3dc6d9484800f0d
author: Simon Howard <[email protected]>
date: Mon Apr 1 10:59:34 EDT 2013

Merge from trunk.

Subversion-branch: /branches/v2-branch
Subversion-revision: 2577

--- a/src/setup/multiplayer.c
+++ b/src/setup/multiplayer.c
@@ -68,6 +68,8 @@
 
 static char *iwadfile;
 
+static char *wad_extensions[] = { "wad", "lmp", "deh", NULL };
+
 static char *doom_skills[] =
 {
     "I'm too young to die.", "Hey, not too rough.", "Hurt me plenty.",
@@ -163,7 +165,7 @@
 {
     int have_wads = 0;
     int i;
-    
+ 
     for (i=0; i<NUM_WADS; ++i)
     {
         if (wads[i] != NULL && strlen(wads[i]) > 0)
@@ -277,7 +279,7 @@
     AddWADs(exec);
 
     TXT_Shutdown();
-    
+ 
     M_SaveDefaults();
     PassThroughArguments(exec);
 
@@ -610,7 +612,9 @@
 
     for (i=0; i<NUM_WADS; ++i)
     {
-        TXT_AddWidget(window, TXT_NewInputBox(&wads[i], 60));
+        TXT_AddWidget(window,
+                      TXT_NewFileSelector(&wads[i], 60, "Select a WAD file",
+                                          wad_extensions));
     }
 }
 
--- a/textscreen/Makefile.am
+++ b/textscreen/Makefile.am
@@ -16,6 +16,7 @@
 	txt_checkbox.c           txt_checkbox.h           \
 	txt_desktop.c            txt_desktop.h            \
 	txt_dropdown.c           txt_dropdown.h           \
+	txt_fileselect.c         txt_fileselect.h         \
 	txt_gui.c                txt_gui.h                \
 	txt_inputbox.c           txt_inputbox.h           \
 	txt_io.c                 txt_io.h                 \
--- a/textscreen/examples/guitest.c
+++ b/textscreen/examples/guitest.c
@@ -39,10 +39,13 @@
     RADIO_VALUE_MUSHROOM,
     RADIO_VALUE_SNAKE,
 };
+char *extensions[] = { "wad", "lmp", "txt", NULL };
 char *radio_values[] = { "Badger", "Mushroom", "Snake" };
 char *textbox_value = NULL;
 int numbox_value = 0;
 int radiobutton_value;
+char *file_path = NULL;
+char *dir_path = NULL;
 txt_label_t *value_label;
 txt_window_t *firstwin;
 int cheesy;
@@ -187,12 +190,20 @@
     TXT_AddWidget(window, TXT_NewSeparator("Input boxes"));
     table = TXT_NewTable(2);
     TXT_AddWidget(window, table);
-    TXT_AddWidget(table, TXT_NewLabel("String: "));
-    TXT_AddWidget(table, TXT_NewInputBox(&textbox_value, 20));
-    TXT_AddWidget(table, TXT_NewLabel("Int: "));
-    TXT_AddWidget(table, TXT_NewIntInputBox(&numbox_value, 10));
-    TXT_AddWidget(table, TXT_NewLabel("Spin control:"));
-    TXT_AddWidget(table, TXT_NewSpinControl(&numbox_value, 0, 15));
+    TXT_AddWidgets(table,
+                   TXT_NewLabel("String: "),
+                   TXT_NewInputBox(&textbox_value, 20),
+                   TXT_NewLabel("Int: "),
+                   TXT_NewIntInputBox(&numbox_value, 10),
+                   TXT_NewLabel("Spin control:"),
+                   TXT_NewSpinControl(&numbox_value, 0, 15),
+                   TXT_NewLabel("File:"),
+                   TXT_NewFileSelector(&file_path, 28, "Select file:",
+                                       extensions),
+                   TXT_NewLabel("Directory:"),
+                   TXT_NewFileSelector(&dir_path, 28, "Select directory:",
+                                       TXT_DIRECTORY),
+                   NULL);
 
     TXT_AddWidget(window, TXT_NewSeparator("Scroll pane test"));
     scrollpane = TXT_NewScrollPane(40, 5, TXT_NewLabel(
--- a/textscreen/textscreen.h
+++ b/textscreen/textscreen.h
@@ -29,6 +29,7 @@
 #include "txt_checkbox.h"
 #include "txt_desktop.h"
 #include "txt_dropdown.h"
+#include "txt_fileselect.h"
 #include "txt_inputbox.h"
 #include "txt_label.h"
 #include "txt_radiobutton.h"
--- /dev/null
+++ b/textscreen/txt_fileselect.c
@@ -1,0 +1,676 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2013 Simon Howard
+//
+// 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., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Routines for selecting files.
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "doomkeys.h"
+
+#include "txt_fileselect.h"
+#include "txt_inputbox.h"
+#include "txt_main.h"
+#include "txt_widget.h"
+
+struct txt_fileselect_s {
+    txt_widget_t widget;
+    txt_inputbox_t *inputbox;
+    int size;
+    char *prompt;
+    char **extensions;
+};
+
+// Dummy value to select a directory.
+
+char *TXT_DIRECTORY[] = { "__directory__", NULL };
+
+#ifndef _WIN32
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+static char *ExecReadOutput(char **argv)
+{
+    char *result;
+    int completed;
+    int pid, status, result_len;
+    int pipefd[2];
+
+    if (pipe(pipefd) != 0)
+    {
+        return NULL;
+    }
+
+    pid = fork();
+
+    if (pid == 0)
+    {
+        dup2(pipefd[1], fileno(stdout));
+        execv(argv[0], argv);
+        exit(-1);
+    }
+
+    fcntl(pipefd[0], F_SETFL, O_NONBLOCK);
+
+    // Read program output into 'result' string.
+    // Wait until the program has completed and (if it was successful)
+    // a full line has been read.
+
+    result = NULL;
+    result_len = 0;
+    completed = 0;
+
+    while (!completed
+        || (status == 0 && (result == NULL || strchr(result, '\n') == NULL)))
+    {
+        char buf[64];
+        int bytes;
+
+        if (!completed && waitpid(pid, &status, WNOHANG) != 0)
+        {
+            completed = 1;
+        }
+
+        bytes = read(pipefd[0], buf, sizeof(buf));
+
+        if (bytes < 0)
+        {
+            if (errno != EAGAIN && errno != EWOULDBLOCK)
+            {
+                status = -1;
+                break;
+            }
+        }
+        else
+        {
+            result = realloc(result, result_len + bytes + 1);
+            memcpy(result + result_len, buf, bytes);
+            result_len += bytes;
+            result[result_len] = '\0';
+        }
+
+        usleep(100 * 1000);
+        TXT_Sleep(1);
+        TXT_UpdateScreen();
+    }
+
+    close(pipefd[0]);
+    close(pipefd[1]);
+
+    // Must have a success exit code.
+
+    if (WEXITSTATUS(status) != 0)
+    {
+        free(result);
+        result = NULL;
+    }
+
+    // Strip off newline from the end.
+
+    if (result != NULL && result[result_len - 1] == '\n')
+    {
+        result[result_len - 1] = '\0';
+    }
+
+    return result;
+}
+
+#endif
+
+#if defined(_WIN32)
+
+// Windows code. Use comdlg32 to pop up a dialog box.
+
+#include <windows.h>
+#include <shlobj.h>
+
+static BOOL WINAPI (*MyGetOpenFileName)(LPOPENFILENAME) = NULL;
+static LPITEMIDLIST (*MySHBrowseForFolder)(LPBROWSEINFO) = NULL;
+static BOOL (*MySHGetPathFromIDList)(LPITEMIDLIST, LPTSTR) = NULL;
+
+// Load library functions from DLL files.
+
+static int LoadDLLs(void)
+{
+    HMODULE comdlg32 = LoadLibraryW(L"comdlg32.dll");
+    HMODULE shell32 = LoadLibraryW(L"shell32.dll");
+
+    if (comdlg32 == NULL || shell32 == NULL)
+    {
+        return 0;
+    }
+
+    MyGetOpenFileName =
+        (void *) GetProcAddress(comdlg32, "GetOpenFileNameA");
+    MySHBrowseForFolder =
+        (void *) GetProcAddress(shell32, "SHBrowseForFolder");
+    MySHGetPathFromIDList =
+        (void *) GetProcAddress(shell32, "SHGetPathFromIDList");
+
+    return MyGetOpenFileName != NULL
+        && MySHBrowseForFolder != NULL
+        && MySHGetPathFromIDList != NULL;
+}
+
+static int InitLibraries(void)
+{
+    static int initted = 0, success = 0;
+
+    if (!initted)
+    {
+        success = LoadDLLs();
+        initted = 1;
+    }
+
+    return success;
+}
+
+// Generate the "filter" string from the list of extensions.
+
+static char *GenerateFilterString(char **extensions)
+{
+    unsigned int result_len = 1;
+    unsigned int i;
+    char *result, *out;
+
+    if (extensions == NULL)
+    {
+        return NULL;
+    }
+
+    for (i = 0; extensions[i] != NULL; ++i)
+    {
+        result_len += 16 + strlen(extensions[i]) * 3;
+    }
+
+    result = malloc(result_len);
+    out = result;
+
+    for (i = 0; extensions[i] != NULL; ++i)
+    {
+        // .wad files (*.wad)\0
+        out += 1 + sprintf(out, "%s files (*.%s)",
+                           extensions[i], extensions[i]);
+        // *.wad\0
+        out += 1 + sprintf(out, "*.%s", extensions[i]);
+    }
+
+    *out = '\0';
+
+    return result;
+}
+
+int TXT_CanSelectFiles(void)
+{
+    return InitLibraries();
+}
+
+static char *SelectDirectory(char *window_title)
+{
+    LPITEMIDLIST pidl;
+    BROWSEINFO bi;
+    char selected[MAX_PATH] = "";
+    char *result;
+
+    ZeroMemory(&bi, sizeof(bi));
+    bi.hwndOwner = NULL;
+    bi.lpszTitle = window_title;
+    bi.pszDisplayName = selected;
+    bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
+
+    pidl = MySHBrowseForFolder(&bi);
+
+    result = NULL;
+
+    if (pidl != NULL)
+    {
+        if (MySHGetPathFromIDList(pidl, selected))
+        {
+            result = strdup(selected);
+        }
+
+        // TODO: Free pidl
+    }
+
+    return result;
+}
+
+char *TXT_SelectFile(char *window_title, char **extensions)
+{
+    OPENFILENAME fm;
+    char selected[MAX_PATH] = "";
+    char *filter_string, *result;
+
+    if (!InitLibraries())
+    {
+        return NULL;
+    }
+
+    if (extensions == TXT_DIRECTORY)
+    {
+        return SelectDirectory(window_title);
+    }
+
+    filter_string = GenerateFilterString(extensions);
+
+    ZeroMemory(&fm, sizeof(fm));
+    fm.lStructSize = sizeof(OPENFILENAME);
+    fm.hwndOwner = NULL;
+    fm.lpstrTitle = window_title;
+    fm.lpstrFilter = filter_string;
+    fm.lpstrFile = selected;
+    fm.nMaxFile = MAX_PATH;
+    fm.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
+    fm.lpstrDefExt = "";
+
+    if (!MyGetOpenFileName(&fm))
+    {
+        result = NULL;
+    }
+    else
+    {
+        result = strdup(selected);
+    }
+
+    free(filter_string);
+
+    return result;
+}
+
+#elif defined(__MACOSX__)
+
+// Mac OS X code. Popping up a dialog requires Objective C/Cocoa
+// but we can get away with using AppleScript which avoids adding
+// an Objective C dependency. This is rather silly.
+
+// Printf format string for the "wrapper" portion of the AppleScript:
+
+#define APPLESCRIPT_WRAPPER \
+    "tell application (path to frontmost application as text)\n" \
+    "    set theFile to (%s)\n" \
+    "    copy POSIX path of theFile to stdout\n" \
+    "end tell\n"
+
+static char *EscapedString(char *s)
+{
+    char *result;
+    char *in, *out;
+
+    result = malloc(strlen(s) + 3);
+    out = result;
+    *out++ = '\"';
+    for (in = s; *in != '\0'; ++in)
+    {
+        if (*in == '\"' || *in == '\\')
+        {
+            *out++ = '\\';
+        }
+        *out++ = *in;
+    }
+    *out++ = '\"';
+    *out = '\0';
+
+    return result;
+}
+
+// Build list of extensions, like: {"wad","lmp","txt"}
+
+static char *ExtensionsList(char **extensions)
+{
+    char *result, *escaped;
+    unsigned int result_len;
+    unsigned int i;
+
+    if (extensions == NULL)
+    {
+        return NULL;
+    }
+
+    result_len = 3;
+    for (i = 0; extensions[i] != NULL; ++i)
+    {
+        result_len += 5 + strlen(extensions[i]) * 2;
+    }
+
+    result = malloc(result_len);
+    strcpy(result, "{");
+
+    for (i = 0; extensions[i] != NULL; ++i)
+    {
+        escaped = EscapedString(extensions[i]);
+        strcat(result, escaped);
+        free(escaped);
+
+        if (extensions[i + 1] != NULL)
+            strcat(result, ",");
+    }
+
+    strcat(result, "}");
+
+    return result;
+}
+
+static char *GenerateSelector(char *window_title, char **extensions)
+{
+    char *chooser, *ext_list, *result;
+    unsigned int result_len;
+
+    result_len = 64;
+
+    if (extensions == TXT_DIRECTORY)
+    {
+        chooser = "choose folder";
+        ext_list = NULL;
+    }
+    else
+    {
+        chooser = "choose file";
+        ext_list = ExtensionsList(extensions);
+    }
+
+    // Calculate size.
+
+    if (window_title != NULL)
+    {
+        window_title = EscapedString(window_title);
+        result_len += strlen(window_title);
+    }
+    if (ext_list != NULL)
+    {
+        result_len += strlen(ext_list);
+    }
+
+    result = malloc(result_len);
+
+    strcpy(result, chooser);
+
+    if (window_title != NULL)
+    {
+        strcat(result, " with prompt ");
+        strcat(result, window_title);
+        free(window_title);
+    }
+
+    if (ext_list != NULL)
+    {
+        strcat(result, "of type ");
+        strcat(result, ext_list);
+        free(ext_list);
+    }
+
+    return result;
+}
+
+static char *GenerateAppleScript(char *window_title, char **extensions)
+{
+    char *selector, *result;
+
+    selector = GenerateSelector(window_title, extensions);
+
+    result = malloc(strlen(APPLESCRIPT_WRAPPER) + strlen(selector));
+    sprintf(result, APPLESCRIPT_WRAPPER, selector);
+    free(selector);
+
+    return result;
+}
+
+int TXT_CanSelectFiles(void)
+{
+    return 1;
+}
+
+char *TXT_SelectFile(char *window_title, char **extensions)
+{
+    char *argv[4];
+    char *result, *applescript;
+
+    applescript = GenerateAppleScript(window_title, extensions);
+
+    argv[0] = "/usr/bin/osascript";
+    argv[1] = "-e";
+    argv[2] = applescript;
+    argv[3] = NULL;
+
+    result = ExecReadOutput(argv);
+
+    free(applescript);
+
+    return result;
+}
+
+#else
+
+// Linux version: invoke the Zenity command line program to pop up a
+// dialog box. This avoids adding Gtk+ as a compile dependency.
+
+#define ZENITY_BINARY "/usr/bin/zenity"
+
+static unsigned int NumExtensions(char **extensions)
+{
+    unsigned int result = 0;
+
+    if (extensions != NULL)
+    {
+        for (result = 0; extensions[result] != NULL; ++result);
+    }
+
+    return result;
+}
+
+static int ZenityAvailable(void)
+{
+    return system(ZENITY_BINARY " --help >/dev/null 2>&1") == 0;
+}
+
+int TXT_CanSelectFiles(void)
+{
+    return ZenityAvailable();
+}
+
+char *TXT_SelectFile(char *window_title, char **extensions)
+{
+    unsigned int i;
+    char *result;
+    char **argv;
+    int argc;
+
+    if (!ZenityAvailable())
+    {
+        return NULL;
+    }
+
+    argv = calloc(4 + NumExtensions(extensions), sizeof(char *));
+    argv[0] = ZENITY_BINARY;
+    argv[1] = "--file-selection";
+    argc = 2;
+
+    if (window_title != NULL)
+    {
+        argv[argc] = malloc(10 + strlen(window_title));
+        sprintf(argv[argc], "--title=%s", window_title);
+        ++argc;
+    }
+
+    if (extensions == TXT_DIRECTORY)
+    {
+        argv[argc] = strdup("--directory");
+        ++argc;
+    }
+    else if (extensions != NULL)
+    {
+        for (i = 0; extensions[i] != NULL; ++i)
+        {
+            argv[argc] = malloc(30 + strlen(extensions[i]) * 2);
+            sprintf(argv[argc], "--file-filter=.%s | *.%s",
+                    extensions[i], extensions[i]);
+            ++argc;
+        }
+    }
+
+    argv[argc] = NULL;
+
+    result = ExecReadOutput(argv);
+
+    for (i = 2; i < argc; ++i)
+    {
+        free(argv[i]);
+    }
+
+    free(argv);
+
+    return result;
+}
+
+#endif
+
+static void TXT_FileSelectSizeCalc(TXT_UNCAST_ARG(fileselect))
+{
+    TXT_CAST_ARG(txt_fileselect_t, fileselect);
+
+    // Calculate widget size, but override the width to always
+    // be the configured size.
+
+    TXT_CalcWidgetSize(fileselect->inputbox);
+    fileselect->widget.w = fileselect->size;
+    fileselect->widget.h = fileselect->inputbox->widget.h;
+}
+
+static void TXT_FileSelectDrawer(TXT_UNCAST_ARG(fileselect))
+{
+    TXT_CAST_ARG(txt_fileselect_t, fileselect);
+
+    // Input box widget inherits all the properties of the
+    // file selector.
+
+    fileselect->inputbox->widget.x = fileselect->widget.x;
+    fileselect->inputbox->widget.y = fileselect->widget.y;
+    fileselect->inputbox->widget.w = fileselect->widget.w;
+    fileselect->inputbox->widget.h = fileselect->widget.h;
+
+    TXT_DrawWidget(fileselect->inputbox);
+}
+
+static void TXT_FileSelectDestructor(TXT_UNCAST_ARG(fileselect))
+{
+    TXT_CAST_ARG(txt_fileselect_t, fileselect);
+
+    TXT_DestroyWidget(fileselect->inputbox);
+}
+
+static int DoSelectFile(txt_fileselect_t *fileselect)
+{
+    char *path;
+    char **var;
+
+    if (TXT_CanSelectFiles())
+    {
+        path = TXT_SelectFile(fileselect->prompt,
+                              fileselect->extensions);
+        var = fileselect->inputbox->value;
+        free(*var);
+        *var = path;
+        return 1;
+    }
+
+    return 0;
+}
+
+static int TXT_FileSelectKeyPress(TXT_UNCAST_ARG(fileselect), int key)
+{
+    TXT_CAST_ARG(txt_fileselect_t, fileselect);
+
+    // When the enter key is pressed, pop up a file selection dialog,
+    // if file selectors work. Allow holding down 'alt' to override
+    // use of the native file selector, so the user can just type a path.
+
+    if (!fileselect->inputbox->editing
+     && !TXT_GetModifierState(TXT_MOD_ALT)
+     && key == KEY_ENTER)
+    {
+        if (DoSelectFile(fileselect))
+        {
+            return 1;
+        }
+    }
+
+    return TXT_WidgetKeyPress(fileselect->inputbox, key);
+}
+
+static void TXT_FileSelectMousePress(TXT_UNCAST_ARG(fileselect),
+                                     int x, int y, int b)
+{
+    TXT_CAST_ARG(txt_fileselect_t, fileselect);
+
+    if (!fileselect->inputbox->editing
+     && !TXT_GetModifierState(TXT_MOD_ALT)
+     && b == TXT_MOUSE_LEFT)
+    {
+        if (DoSelectFile(fileselect))
+        {
+            return;
+        }
+    }
+
+    return TXT_WidgetMousePress(fileselect->inputbox, x, y, b);
+}
+
+static void TXT_FileSelectFocused(TXT_UNCAST_ARG(fileselect), int focused)
+{
+    TXT_CAST_ARG(txt_fileselect_t, fileselect);
+
+    TXT_SetWidgetFocus(fileselect->inputbox, focused);
+}
+
+txt_widget_class_t txt_fileselect_class =
+{
+    TXT_AlwaysSelectable,
+    TXT_FileSelectSizeCalc,
+    TXT_FileSelectDrawer,
+    TXT_FileSelectKeyPress,
+    TXT_FileSelectDestructor,
+    TXT_FileSelectMousePress,
+    NULL,
+    TXT_FileSelectFocused,
+};
+
+txt_fileselect_t *TXT_NewFileSelector(char **variable, int size,
+                                      char *prompt, char **extensions)
+{
+    txt_fileselect_t *fileselect;
+
+    fileselect = malloc(sizeof(txt_fileselect_t));
+    TXT_InitWidget(fileselect, &txt_fileselect_class);
+    fileselect->inputbox = TXT_NewInputBox(variable, 1024);
+    fileselect->inputbox->widget.parent = &fileselect->widget;
+    fileselect->size = size;
+    fileselect->prompt = prompt;
+    fileselect->extensions = extensions;
+
+    return fileselect;
+}
+
--- /dev/null
+++ b/textscreen/txt_fileselect.h
@@ -1,0 +1,88 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2013 Simon Howard
+//
+// 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., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+//
+// Routines for selecting files, and the txt_fileselect_t widget.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef TXT_FILESELECT_H
+#define TXT_FILESELECT_H
+
+/**
+ * @file txt_fileselect.h
+ *
+ * File selection widget.
+ */
+
+/**
+ * File selection widget.
+ *
+ * A file selection widget resembles an input box (@ref txt_inputbox_t)
+ * but opens a file selector dialog box when clicked.
+ */
+
+typedef struct txt_fileselect_s txt_fileselect_t;
+
+/**
+ * Returns non-zero if a native file selector is available on this
+ * platform.
+ */
+
+int TXT_CanSelectFiles(void);
+
+/**
+ * Open a native file selector to select a file.
+ *
+ * @param prompt      Pointer to a string containing a prompt to display
+ *                    in the window.
+ * @param extensions  NULL-terminated list of filename extensions for
+ *                    files that can be selected, or @ref TXT_DIRECTORY
+ *                    to select directories.
+ */
+
+char *TXT_SelectFile(char *prompt, char **extensions);
+
+/**
+ * Create a new txt_fileselect_t widget.
+ *
+ * @param variable    Pointer to a char * variable in which the selected
+ *                    file should be stored.
+ * @param size        Width of the file selector widget in characters.
+ * @param prompt      Pointer to a string containing a prompt to display
+ *                    in the file selection window.
+ * @param extensions  NULL-terminated list of filename extensions that
+ *                    can be used for this widget, or @ref TXT_DIRECTORY
+ *                    to select directories.
+ */
+
+txt_fileselect_t *TXT_NewFileSelector(char **variable, int size,
+                                      char *prompt, char **extensions);
+
+/**
+ * Special value to use for 'extensions' that selects a directory
+ * instead of a file.
+ */
+
+extern char *TXT_DIRECTORY[];
+
+#endif /* #ifndef TXT_FILESELECT_H */
+
--- a/textscreen/txt_inputbox.c
+++ b/textscreen/txt_inputbox.c
@@ -139,9 +139,21 @@
         SetBufferFromValue(inputbox);
     }
 
-    TXT_DrawUTF8String(inputbox->buffer);
+    // If string size exceeds the widget's width, show only the end.
 
-    chars = TXT_UTF8_Strlen(inputbox->buffer);
+    if (TXT_UTF8_Strlen(inputbox->buffer) > w - 1)
+    {
+        TXT_DrawString("\xae");
+        TXT_DrawUTF8String(
+            TXT_UTF8_SkipChars(inputbox->buffer,
+                               TXT_UTF8_Strlen(inputbox->buffer) - w + 2));
+        chars = w - 1;
+    }
+    else
+    {
+        TXT_DrawUTF8String(inputbox->buffer);
+        chars = TXT_UTF8_Strlen(inputbox->buffer);
+    }
 
     if (chars < w && inputbox->editing && focused)
     {
@@ -202,6 +214,15 @@
         {
             StartEditing(inputbox);
             return 1;
+        }
+
+        // Backspace or delete erases the contents of the box.
+
+        if ((key == KEY_DEL || key == KEY_BACKSPACE)
+         && inputbox->widget.widget_class == &txt_inputbox_class)
+        {
+            free(*((char **)inputbox->value));
+            *((char **) inputbox->value) = strdup("");
         }
 
         return 0;
--- a/textscreen/txt_widget.h
+++ b/textscreen/txt_widget.h
@@ -168,7 +168,6 @@
  * whether it is selected and the mouse is hovering over it.
  *
  * @param widget       The widget.
- * @param selected     Whether the widget is selected.
  */
 
 void TXT_SetWidgetBG(TXT_UNCAST_ARG(widget));