fruitjam-doom/textscreen/txt_table.c
Simon Howard 7684ddcfd8 Clean up file headers.
This change rewrites and simplifies the copyright headers at the top
of all source files:

 * Remove "Emacs style mode select" line; this line was included in
   the headers for the originally released source files and appears
   to be to set the file type for old versions of Emacs. I'm not sure
   entirely why it was required but I don't think it is any more.
 * Remove "You should have received a copy of..." text from copyright
   header. This refers to the old 59 Temple Place address where the
   FSF headquarters used to be located and is no longer correct.
   Rather than change to the new address, just remove the paragraph
   as it is superfluous anyway. This fixes #311.
 * Remove ---- separator lines so that the file headers are barer
   and more simplified.
2014-05-05 00:20:54 -04:00

938 lines
20 KiB
C

//
// Copyright(C) 2005-2014 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.
//
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "doomkeys.h"
#include "txt_desktop.h"
#include "txt_gui.h"
#include "txt_io.h"
#include "txt_main.h"
#include "txt_separator.h"
#include "txt_strut.h"
#include "txt_table.h"
// Remove all entries from a table
void TXT_ClearTable(TXT_UNCAST_ARG(table))
{
TXT_CAST_ARG(txt_table_t, table);
int i;
// Free all widgets
// Skip over the first (num_columns) widgets in the array, as these
// are the column struts used to control column width
for (i=table->columns; i<table->num_widgets; ++i)
{
if (table->widgets[i] != NULL)
{
TXT_DestroyWidget(table->widgets[i]);
}
}
// Shrink the table to just the column strut widgets
table->num_widgets = table->columns;
}
static void TXT_TableDestructor(TXT_UNCAST_ARG(table))
{
TXT_CAST_ARG(txt_table_t, table);
TXT_ClearTable(table);
}
static int TableRows(txt_table_t *table)
{
return (table->num_widgets + table->columns - 1) / table->columns;
}
static void CalcRowColSizes(txt_table_t *table,
unsigned int *row_heights,
unsigned int *col_widths)
{
int x, y;
int rows;
txt_widget_t *widget;
rows = TableRows(table);
memset(col_widths, 0, sizeof(int) * table->columns);
for (y=0; y<rows; ++y)
{
row_heights[y] = 0;
for (x=0; x<table->columns; ++x)
{
if (y * table->columns + x >= table->num_widgets)
break;
widget = table->widgets[y * table->columns + x];
// NULL represents an empty spacer
if (widget != NULL)
{
TXT_CalcWidgetSize(widget);
if (widget->h > row_heights[y])
row_heights[y] = widget->h;
if (widget->w > col_widths[x])
col_widths[x] = widget->w;
}
}
}
}
static void TXT_CalcTableSize(TXT_UNCAST_ARG(table))
{
TXT_CAST_ARG(txt_table_t, table);
unsigned int *column_widths;
unsigned int *row_heights;
int x, y;
int rows;
rows = TableRows(table);
row_heights = malloc(sizeof(int) * rows);
column_widths = malloc(sizeof(int) * table->columns);
CalcRowColSizes(table, row_heights, column_widths);
table->widget.w = 0;
for (x=0; x<table->columns; ++x)
{
table->widget.w += column_widths[x];
}
table->widget.h = 0;
for (y=0; y<rows; ++y)
{
table->widget.h += row_heights[y];
}
free(row_heights);
free(column_widths);
}
void TXT_AddWidget(TXT_UNCAST_ARG(table), TXT_UNCAST_ARG(widget))
{
TXT_CAST_ARG(txt_table_t, table);
TXT_CAST_ARG(txt_widget_t, widget);
if (table->num_widgets > 0)
{
txt_widget_t *last_widget;
last_widget = table->widgets[table->num_widgets - 1];
if (widget != NULL && last_widget != NULL
&& widget->widget_class == &txt_separator_class
&& last_widget->widget_class == &txt_separator_class)
{
// The previous widget added was a separator; replace
// it with this one.
//
// This way, if the first widget added to a window is
// a separator, it replaces the "default" separator that
// the window itself adds on creation.
table->widgets[table->num_widgets - 1] = widget;
TXT_DestroyWidget(last_widget);
return;
}
}
table->widgets = realloc(table->widgets,
sizeof(txt_widget_t *) * (table->num_widgets + 1));
table->widgets[table->num_widgets] = widget;
++table->num_widgets;
// Maintain parent pointer.
if (widget != NULL)
{
widget->parent = &table->widget;
}
}
// Add multiple widgets to a table.
void TXT_AddWidgets(TXT_UNCAST_ARG(table), ...)
{
TXT_CAST_ARG(txt_table_t, table);
va_list args;
txt_widget_t *widget;
va_start(args, TXT_UNCAST_ARG_NAME(table));
// Keep adding widgets until a NULL is reached.
for (;;)
{
widget = va_arg(args, txt_widget_t *);
if (widget == NULL)
{
break;
}
TXT_AddWidget(table, widget);
}
va_end(args);
}
static int SelectableCell(txt_table_t *table, int x, int y)
{
txt_widget_t *widget;
int i;
if (x < 0 || x >= table->columns)
{
return 0;
}
i = y * table->columns + x;
if (i >= 0 && i < table->num_widgets)
{
widget = table->widgets[i];
return widget != NULL
&& TXT_SelectableWidget(widget)
&& widget->visible;
}
return 0;
}
// Tries to locate a selectable widget in the given row, returning
// the column number of the first column available or -1 if none are
// available in the given row.
//
// Starts from start_col, then searches nearby columns.
static int FindSelectableColumn(txt_table_t *table, int row, int start_col)
{
int x;
for (x=0; x<table->columns; ++x)
{
// Search to the right
if (SelectableCell(table, start_col + x, row))
{
return start_col + x;
}
// Search to the left
if (SelectableCell(table, start_col - x, row))
{
return start_col - x;
}
}
// None available
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);
int selected;
int rows;
rows = TableRows(table);
// Send to the currently selected widget first
selected = table->selected_y * table->columns + table->selected_x;
if (selected >= 0 && selected < table->num_widgets)
{
if (table->widgets[selected] != NULL
&& TXT_SelectableWidget(table->widgets[selected])
&& TXT_WidgetKeyPress(table->widgets[selected], key))
{
return 1;
}
}
if (key == KEY_DOWNARROW)
{
int new_x, new_y;
// Move cursor down to the next selectable widget
for (new_y = table->selected_y + 1; new_y < rows; ++new_y)
{
new_x = FindSelectableColumn(table, new_y, table->selected_x);
if (new_x >= 0)
{
// Found a selectable widget in this column!
ChangeSelection(table, new_x, new_y);
return 1;
}
}
}
if (key == KEY_UPARROW)
{
int new_x, new_y;
// Move cursor up to the next selectable widget
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!
ChangeSelection(table, new_x, new_y);
return 1;
}
}
}
if (key == KEY_LEFTARROW)
{
int new_x;
// Move cursor left
for (new_x = table->selected_x - 1; new_x >= 0; --new_x)
{
if (SelectableCell(table, new_x, table->selected_y))
{
// Found a selectable widget!
ChangeSelection(table, new_x, table->selected_y);
return 1;
}
}
}
if (key == KEY_RIGHTARROW)
{
int new_x;
// Move cursor left
for (new_x = table->selected_x + 1; new_x < table->columns; ++new_x)
{
if (SelectableCell(table, new_x, table->selected_y))
{
// Found a selectable widget!
ChangeSelection(table, new_x, table->selected_y);
return 1;
}
}
}
return 0;
}
// Check the currently selected widget in the table is valid.
static void CheckValidSelection(txt_table_t *table)
{
int rows;
int new_x, new_y;
rows = TableRows(table);
for (new_y = table->selected_y; new_y < rows; ++new_y)
{
new_x = FindSelectableColumn(table, new_y, table->selected_x);
if (new_x >= 0)
{
// Found a selectable column.
ChangeSelection(table, new_x, new_y);
break;
}
}
}
static void LayoutCell(txt_table_t *table, int x, int y, int col_width,
int draw_x, int draw_y)
{
txt_widget_t *widget;
widget = table->widgets[y * table->columns + x];
// Adjust x position based on alignment property
switch (widget->align)
{
case TXT_HORIZ_LEFT:
widget->w = col_width;
break;
case TXT_HORIZ_CENTER:
TXT_CalcWidgetSize(widget);
// Separators are always drawn left-aligned.
if (widget->widget_class != &txt_separator_class)
{
draw_x += (col_width - widget->w) / 2;
}
break;
case TXT_HORIZ_RIGHT:
TXT_CalcWidgetSize(widget);
if (widget->widget_class != &txt_separator_class)
{
draw_x += col_width - widget->w;
}
break;
}
// Set the position for this widget
widget->x = draw_x;
widget->y = draw_y;
// Recursively lay out any widgets contained in the widget
TXT_LayoutWidget(widget);
}
static void TXT_TableLayout(TXT_UNCAST_ARG(table))
{
TXT_CAST_ARG(txt_table_t, table);
unsigned int *column_widths;
unsigned int *row_heights;
int draw_x, draw_y;
int x, y;
int i;
int rows;
// Work out the column widths and row heights
rows = TableRows(table);
column_widths = malloc(sizeof(int) * table->columns);
row_heights = malloc(sizeof(int) * rows);
CalcRowColSizes(table, row_heights, column_widths);
// If this table only has one column, expand column size to fit
// the display width. Ensures that separators reach the window edges
// when drawing windows.
if (table->columns == 1)
{
column_widths[0] = table->widget.w;
}
// Draw all cells
draw_y = table->widget.y;
for (y=0; y<rows; ++y)
{
draw_x = table->widget.x;
for (x=0; x<table->columns; ++x)
{
i = y * table->columns + x;
if (i >= table->num_widgets)
break;
if (table->widgets[i] != NULL)
{
LayoutCell(table, x, y, column_widths[x],
draw_x, draw_y);
}
draw_x += column_widths[x];
}
draw_y += row_heights[y];
}
free(row_heights);
free(column_widths);
}
static void TXT_TableDrawer(TXT_UNCAST_ARG(table))
{
TXT_CAST_ARG(txt_table_t, table);
txt_widget_t *widget;
int i;
// Check the table's current selection points at something valid before
// drawing.
CheckValidSelection(table);
// Draw all cells
for (i=0; i<table->num_widgets; ++i)
{
widget = table->widgets[i];
if (widget != NULL)
{
TXT_GotoXY(widget->x, widget->y);
TXT_DrawWidget(widget);
}
}
}
// Responds to mouse presses
static void TXT_TableMousePress(TXT_UNCAST_ARG(table), int x, int y, int b)
{
TXT_CAST_ARG(txt_table_t, table);
txt_widget_t *widget;
int i;
for (i=0; i<table->num_widgets; ++i)
{
widget = table->widgets[i];
// NULL widgets are spacers
if (widget != NULL)
{
if (x >= widget->x && x < (signed) (widget->x + widget->w)
&& y >= widget->y && y < (signed) (widget->y + widget->h))
{
// This is the widget that was clicked!
// Select the cell if the widget is selectable
if (TXT_SelectableWidget(widget))
{
ChangeSelection(table, i % table->columns,
i / table->columns);
}
// Propagate click
TXT_WidgetMousePress(widget, x, y, b);
break;
}
}
}
}
// 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]))
{
ChangeSelection(table, i % table->columns, i / table->columns);
return 1;
}
}
// No selectable widgets exist within the 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,
TXT_CalcTableSize,
TXT_TableDrawer,
TXT_TableKeyPress,
TXT_TableDestructor,
TXT_TableMousePress,
TXT_TableLayout,
TXT_TableFocused,
};
void TXT_InitTable(txt_table_t *table, int columns)
{
int i;
TXT_InitWidget(table, &txt_table_class);
table->columns = columns;
table->widgets = NULL;
table->num_widgets = 0;
table->selected_x = 0;
table->selected_y = 0;
// Add a strut for each column at the start of the table.
// These are used by the TXT_SetColumnWidths function below:
// the struts are created with widths of 0 each, but this
// function changes them.
for (i=0; i<columns; ++i)
{
TXT_AddWidget(table, TXT_NewStrut(0, 0));
}
}
txt_table_t *TXT_NewTable(int columns)
{
txt_table_t *table;
table = malloc(sizeof(txt_table_t));
TXT_InitTable(table, columns);
return table;
}
// Create a horizontal table from a list of widgets.
txt_table_t *TXT_NewHorizBox(TXT_UNCAST_ARG(first_widget), ...)
{
TXT_CAST_ARG(txt_widget_t, first_widget);
txt_table_t *result;
va_list args;
int num_args;
// First, find the number of arguments to determine the width of
// the box.
va_start(args, TXT_UNCAST_ARG_NAME(first_widget));
num_args = 1;
for (;;)
{
txt_widget_t *widget;
widget = va_arg(args, txt_widget_t *);
if (widget == NULL)
{
// End of list
break;
}
else
{
++num_args;
}
}
va_end(args);
// Create the table.
result = TXT_NewTable(num_args);
TXT_AddWidget(result, first_widget);
// Go through the list again and add each widget.
va_start(args, TXT_UNCAST_ARG_NAME(first_widget));
for (;;)
{
txt_widget_t *widget;
widget = va_arg(args, txt_widget_t *);
if (widget == NULL)
{
// End of list
break;
}
else
{
TXT_AddWidget(result, widget);
}
}
va_end(args);
return result;
}
// Get the currently-selected widget in a table, recursively searching
// through sub-tables if necessary.
txt_widget_t *TXT_GetSelectedWidget(TXT_UNCAST_ARG(table))
{
TXT_CAST_ARG(txt_table_t, table);
txt_widget_t *result;
int index;
index = table->selected_y * table->columns + table->selected_x;
result = NULL;
if (index >= 0 && index < table->num_widgets)
{
result = table->widgets[index];
}
if (result != NULL && result->widget_class == &txt_table_class)
{
result = TXT_GetSelectedWidget(result);
}
return result;
}
// Selects a given widget in a table, recursively searching any tables
// within this table. Returns 1 if successful, 0 if unsuccessful.
int TXT_SelectWidget(TXT_UNCAST_ARG(table), TXT_UNCAST_ARG(widget))
{
TXT_CAST_ARG(txt_table_t, table);
TXT_CAST_ARG(txt_widget_t, widget);
int i;
for (i=0; i<table->num_widgets; ++i)
{
if (table->widgets[i] == NULL)
{
continue;
}
if (table->widgets[i] == widget)
{
// Found the item! Select it and return.
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.
if (TXT_SelectWidget(table->widgets[i], widget))
{
// Found it in the subtable. Select this subtable and return.
ChangeSelection(table, i % table->columns, i / table->columns);
return 1;
}
}
}
// Not found.
return 0;
}
// Sets the widths of columns in a table.
void TXT_SetColumnWidths(TXT_UNCAST_ARG(table), ...)
{
TXT_CAST_ARG(txt_table_t, table);
va_list args;
txt_strut_t *strut;
int i;
int width;
va_start(args, TXT_UNCAST_ARG_NAME(table));
for (i=0; i<table->columns; ++i)
{
width = va_arg(args, int);
strut = (txt_strut_t *) table->widgets[i];
strut->width = width;
}
va_end(args);
}
// Moves the select by at least the given number of characters.
// Currently quietly ignores pagex, as we don't use it.
int TXT_PageTable(TXT_UNCAST_ARG(table), int pagex, int pagey)
{
TXT_CAST_ARG(txt_table_t, table);
unsigned int *column_widths;
unsigned int *row_heights;
int rows;
int changed = 0;
rows = TableRows(table);
row_heights = malloc(sizeof(int) * rows);
column_widths = malloc(sizeof(int) * table->columns);
CalcRowColSizes(table, row_heights, column_widths);
if (pagex)
{
// @todo Jump selection to the left or right as needed
}
if (pagey)
{
int new_x, new_y;
int distance = 0;
int dir;
// What direction are we moving?
if (pagey > 0)
{
dir = 1;
}
else
{
dir = -1;
}
// Move the cursor until the desired distance is reached.
new_y = table->selected_y;
while (new_y >= 0 && new_y < rows)
{
// We are about to travel a distance equal to the height of the row
// we are about to leave.
distance += row_heights[new_y];
// *Now* increment the loop.
new_y += dir;
new_x = FindSelectableColumn(table, new_y, table->selected_x);
if (new_x >= 0)
{
// Found a selectable widget in this column!
// Select it anyway in case we don't find something better.
ChangeSelection(table, new_x, new_y);
changed = 1;
// ...but is it far enough away?
if (distance >= abs(pagey))
{
break;
}
}
}
}
free(row_heights);
free(column_widths);
return changed;
}