(svn r1894) - Codechange: cleaned up the console a bit, wholly unified handling of text with that of editboxes

- Codechange: Introduction of Textbuf struct which not only holds physical data as length but also pixel-constrains (width) and information about the caret
- Codechange: Move Clipboard function to OS specific file. Currently only Windows has clipboard actions
- Feature: Editboxes, console and exit screen also accept the numeric-enter as a yes
- Feature: Navigation through text with cursor keys is possible, as well as arbitrary insertion (also paste) and deletion; both backspace and del keys. Functions DeleteTextBufferChar, InsertTextBufferChar and InsertTextBufferClipboard handle input and deletion. Navigation is done through MoveTextBufferPos.
- Fix: OTTD crash when opening 'add server' editbox
- CodeChange: fix up some stringwidth calculations in gfx.c. You can get the width in pixels of a character by calling GetCharacterWidth().
This commit is contained in:
Darkvater
2005-02-21 18:59:54 +00:00
parent 0d8dcacee2
commit d865c55007
14 changed files with 348 additions and 252 deletions

View File

@@ -16,11 +16,7 @@
#include "town.h"
#include "sound.h"
#include "network.h"
// Windows stuff for Clipboard
#if defined(WIN32)
#include <windows.h>
#endif
#include "string.h"
#include "hal.h" // for file list
@@ -779,77 +775,140 @@ void SetHScrollCount(Window *w, int num)
if (num < w->hscroll.pos) w->hscroll.pos = num;
}
/* Get the count of characters in the string as well as the width in pixels
* [IN]buf: string to be checked
* [OUT]count: gets set to the count of characters
* [OUT]width: gets set to the pixels width */
static void GetCurrentStringSize(const char *buf, int *count, int *width)
static void DelChar(Textbuf *tb)
{
*count = 0;
*width = -1;
tb->width -= GetCharacterWidth(tb->buf[tb->caretpos]);
memmove(tb->buf + tb->caretpos, tb->buf + tb->caretpos + 1, tb->length - tb->caretpos);
tb->length--;
}
do {
if (*++buf == 0)
break;
(*count)++;
(*width) += _stringwidth_table[(byte)*buf - 32];
} while (1);
/**
* Delete a character from a textbuffer, either with 'Delete' or 'Backspace'
* The character is delete from the position the caret is at
* @param tb @Textbuf type to be changed
* @param delmode Type of deletion, either @WKC_BACKSPACE or @WKC_DELETE
* @return Return true on successfull change of Textbuf, or false otherwise
*/
bool DeleteTextBufferChar(Textbuf *tb, int delmode)
{
if (delmode == WKC_BACKSPACE && tb->caretpos != 0) {
tb->caretpos--;
tb->caretxoffs -= GetCharacterWidth(tb->buf[tb->caretpos]);
DelChar(tb);
return true;
} else if (delmode == WKC_DELETE && tb->caretpos < tb->length) {
DelChar(tb);
return true;
}
return false;
}
/**
* Insert a character to a textbuffer. If maxlength is zero, we don't care about
* the screenlength but only about the physical length of the string
* @param tb @Textbuf type to be changed
* @param key Character to be inserted
* @return Return true on successfull change of Textbuf, or false otherwise
*/
bool InsertTextBufferChar(Textbuf *tb, byte key)
{
const byte charwidth = GetCharacterWidth(key);
if (tb->length < tb->maxlength && (tb->maxwidth == 0 || tb->width + charwidth <= tb->maxwidth)) {
memmove(tb->buf + tb->caretpos + 1, tb->buf + tb->caretpos, tb->length - tb->caretpos);
tb->buf[tb->caretpos] = key;
tb->length++;
tb->width += charwidth;
tb->caretpos++;
tb->caretxoffs += charwidth;
return true;
}
return false;
}
/**
* Handle text navigation with arrow keys left/right.
* This defines where the caret will blink and the next characer interaction will occur
* @param tb @Textbuf type where navigation occurs
* @param navmode Direction in which navigation occurs @WKC_LEFT, @WKC_RIGHT, @WKC_END, @WKC_HOME
* @return Return true on successfull change of Textbuf, or false otherwise
*/
bool MoveTextBufferPos(Textbuf *tb, int navmode)
{
switch (navmode) {
case WKC_LEFT:
if (tb->caretpos != 0) {
tb->caretpos--;
tb->caretxoffs -= GetCharacterWidth(tb->buf[tb->caretpos]);
return true;
}
break;
case WKC_RIGHT:
if (tb->caretpos < tb->length) {
tb->caretxoffs += GetCharacterWidth(tb->buf[tb->caretpos]);
tb->caretpos++;
return true;
}
break;
case WKC_HOME:
tb->caretpos = 0;
tb->caretxoffs = 0;
return true;
case WKC_END:
tb->caretpos = tb->length;
tb->caretxoffs = tb->width;
return true;
}
return false;
}
/**
* Update @Textbuf type with its actual physical character and screenlength
* Get the count of characters in the string as well as the width in pixels.
* Useful when copying in a larger amount of text at once
* @param tb @Textbuf type which length is calculated
*/
void UpdateTextBufferSize(Textbuf *tb)
{
char *buf;
tb->length = 0;
tb->width = 0;
for (buf = tb->buf; *buf != '\0' && tb->length <= tb->maxlength; buf++) {
tb->length++;
tb->width += GetCharacterWidth((byte)*buf);
}
tb->caretpos = tb->length;
tb->caretxoffs = tb->width;
}
int HandleEditBoxKey(Window *w, int wid, WindowEvent *we)
{
int width,count;
int key = we->keypress.ascii;
we->keypress.cont = false;
if (we->keypress.keycode == WKC_ESC) {
return 2;
} else if (we->keypress.keycode == WKC_RETURN) {
return 1;
#ifdef WIN32
} else if (we->keypress.keycode == (WKC_CTRL | 'V')) {
if (IsClipboardFormatAvailable(CF_TEXT)) {
const byte* data;
HGLOBAL cbuf;
OpenClipboard(NULL);
cbuf = GetClipboardData(CF_TEXT);
data = GlobalLock(cbuf); // clipboard data
GetCurrentStringSize(WP(w,querystr_d).buf - 1, &count, &width);
/* IS_INT_INSIDE = filter for ascii-function codes like BELL and so on [we need an special filter here later] */
for (; (IS_INT_INSIDE(*data, ' ', 256)) && // valid ASCII char
(count < WP(w,querystr_d).maxlen - 1 && // max charcount; always allow for terminating '\0'
width + _stringwidth_table[(int)(*data) - 32] <= WP(w,querystr_d).maxwidth); ++data) { // max screensize
// append data and update size parameters
WP(w,querystr_d).buf[count] = *data;
count++;
width += _stringwidth_table[*data - 32];
}
WP(w,querystr_d).buf[count + 1] = '\0';
GlobalUnlock(cbuf);
CloseClipboard();
switch (we->keypress.keycode) {
case WKC_ESC: return 2;
case WKC_RETURN: case WKC_NUM_ENTER: return 1;
case (WKC_CTRL | 'V'):
if (InsertTextBufferClipboard(&WP(w, querystr_d).text))
InvalidateWidget(w, wid);
}
#endif
} else {
GetCurrentStringSize(WP(w,querystr_d).buf - 1, &count, &width);
if (we->keypress.keycode == WKC_BACKSPACE) {
if (count != 0) {
WP(w,querystr_d).buf[count-1] = 0;
break;
case WKC_BACKSPACE: case WKC_DELETE:
if (DeleteTextBufferChar(&WP(w, querystr_d).text, we->keypress.keycode))
InvalidateWidget(w, wid);
break;
case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
if (MoveTextBufferPos(&WP(w, querystr_d).text, we->keypress.keycode))
InvalidateWidget(w, wid);
break;
default:
if (IsValidAsciiChar(we->keypress.ascii)) {
if (InsertTextBufferChar(&WP(w, querystr_d).text, we->keypress.ascii))
InvalidateWidget(w, wid);
}
} else if (IS_INT_INSIDE((key = we->keypress.ascii), 32, 256)) {
if (count < WP(w,querystr_d).maxlen && width + _stringwidth_table[key - 32] <= WP(w,querystr_d).maxwidth) {
WP(w,querystr_d).buf[count] = key;
WP(w,querystr_d).buf[count + 1] = '\0';
InvalidateWidget(w, wid);
}
} else // key wasn't caught
we->keypress.cont = true;
}
@@ -857,42 +916,45 @@ int HandleEditBoxKey(Window *w, int wid, WindowEvent *we)
return 0;
}
bool HandleCaret(Textbuf *tb)
{
/* caret changed? */
bool b = !!(_caret_timer & 0x20);
if (b != tb->caret) {
tb->caret = b;
return true;
}
return false;
}
void HandleEditBox(Window *w, int wid)
{
bool b;
/* caret changed? */
b = !!(_caret_timer & 0x20);
if (b != WP(w,querystr_d).caret) {
WP(w,querystr_d).caret = b;
if (HandleCaret(&WP(w, querystr_d).text))
InvalidateWidget(w, wid);
}
}
void DrawEditBox(Window *w, int wid)
{
const Widget *wi = w->widget + wid;
int x;
const Textbuf *tb = &WP(w,querystr_d).text;
GfxFillRect(wi->left+1, wi->top+1, wi->right-1, wi->bottom-1, 215);
x = DoDrawString(WP(w,querystr_d).buf, wi->left+2, wi->top+1, 8);
if (WP(w,querystr_d).caret)
DoDrawString("_", x, wi->top+1, 12);
DoDrawString(tb->buf, wi->left+2, wi->top+1, 8);
if (tb->caret)
DoDrawString("_", wi->left + 2 + tb->caretxoffs, wi->top + 1, 12);
}
static void QueryStringWndProc(Window *w, WindowEvent *e)
{
static bool closed = false;
switch(e->event) {
case WE_PAINT: {
// int x;
case WE_PAINT:
SetDParam(0, WP(w,querystr_d).caption);
DrawWindowWidgets(w);
DrawEditBox(w, 5);
} break;
break;
case WE_CLICK:
switch(e->click.widget) {
@@ -900,10 +962,10 @@ static void QueryStringWndProc(Window *w, WindowEvent *e)
case 4:
press_ok:;
if (WP(w, querystr_d).orig != NULL &&
strcmp(WP(w, querystr_d).buf, WP(w, querystr_d).orig) == 0) {
strcmp(WP(w, querystr_d).text.buf, WP(w, querystr_d).orig) == 0) {
DeleteWindow(w);
} else {
char *buf = WP(w,querystr_d).buf;
char *buf = WP(w,querystr_d).text.buf;
WindowClass wnd_class = WP(w,querystr_d).wnd_class;
WindowNumber wnd_num = WP(w,querystr_d).wnd_num;
Window *parent;
@@ -945,6 +1007,7 @@ press_ok:;
case WE_CREATE:
closed = false;
_editbox_win = w;
break;
case WE_DESTROY:
@@ -958,6 +1021,7 @@ press_ok:;
}
}
_query_string_active = false;
_editbox_win = NULL;
break;
}
}
@@ -986,8 +1050,9 @@ static char _orig_str_buf[lengthof(_edit_str_buf)];
void ShowQueryString(StringID str, StringID caption, uint maxlen, uint maxwidth, WindowClass window_class, WindowNumber window_number)
{
Window *w;
uint realmaxlen = maxlen & ~0x1000;
assert(maxlen < lengthof(_edit_str_buf));
assert(realmaxlen < lengthof(_edit_str_buf));
DeleteWindowById(WC_QUERY_STRING, 0);
DeleteWindowById(WC_SAVELOAD, 0);
@@ -995,24 +1060,24 @@ void ShowQueryString(StringID str, StringID caption, uint maxlen, uint maxwidth,
w = AllocateWindowDesc(&_query_string_desc);
GetString(_edit_str_buf, str);
_edit_str_buf[maxlen] = '\0';
_edit_str_buf[realmaxlen] = '\0';
if (maxlen & 0x1000) {
WP(w, querystr_d).orig = NULL;
maxlen &= ~0x1000;
} else {
strcpy(_orig_str_buf, _edit_str_buf);
WP(w, querystr_d).orig = _orig_str_buf;
}
w->click_state = 1 << 5;
WP(w,querystr_d).caption = caption;
WP(w,querystr_d).wnd_class = window_class;
WP(w,querystr_d).wnd_num = window_number;
WP(w,querystr_d).caret = 0;
WP(w,querystr_d).maxlen = maxlen;
WP(w,querystr_d).maxwidth = maxwidth;
WP(w,querystr_d).buf = _edit_str_buf;
WP(w, querystr_d).caption = caption;
WP(w, querystr_d).wnd_class = window_class;
WP(w, querystr_d).wnd_num = window_number;
WP(w, querystr_d).text.caret = false;
WP(w, querystr_d).text.maxlength = realmaxlen - 1;
WP(w, querystr_d).text.maxwidth = maxwidth;
WP(w, querystr_d).text.buf = _edit_str_buf;
UpdateTextBufferSize(&WP(w, querystr_d).text);
_query_string_active = true;
}
@@ -1220,7 +1285,8 @@ static void SaveLoadDlgWndProc(Window *w, WindowEvent *e)
DeleteWindow(w);
} else {
// SLD_SAVE_GAME, SLD_SAVE_SCENARIO copy clicked name to editbox
strcpy(WP(w,querystr_d).buf, file->title[0] ? file->title : file->name);
ttd_strlcpy(WP(w, querystr_d).text.buf, (file->title[0] != '\0') ? file->title : file->name, WP(w, querystr_d).text.maxlength);
UpdateTextBufferSize(&WP(w, querystr_d).text);
InvalidateWidget(w, 9);
}
} else {
@@ -1246,14 +1312,14 @@ static void SaveLoadDlgWndProc(Window *w, WindowEvent *e)
break;
case WE_TIMEOUT:
if (HASBIT(w->click_state, 10)) { /* Delete button clicked */
FiosDelete(WP(w,querystr_d).buf);
FiosDelete(WP(w,querystr_d).text.buf);
SetWindowDirty(w);
BuildFileList();
if (_saveload_mode == SLD_SAVE_GAME)
GenerateFileName(); /* Reset file name to current date */
} else if (HASBIT(w->click_state, 11)) { /* Save button clicked */
_switch_mode = SM_SAVE;
FiosMakeSavegameName(_file_to_saveload.name, WP(w,querystr_d).buf);
FiosMakeSavegameName(_file_to_saveload.name, WP(w,querystr_d).text.buf);
/* In the editor set up the vehicle engines correctly (date might have changed) */
if (_game_mode == GM_EDITOR) StartupEngines();
@@ -1339,17 +1405,17 @@ void ShowSaveLoadDialog(int mode)
w->resize.step_width = 2;
w->resize.step_height = 10;
w->resize.height = w->height - 14 * 10; // Minimum of 10 items
w->click_state |= (1 << 6);
WP(w,querystr_d).caret = 0;
WP(w,querystr_d).maxlen = lengthof(_edit_str_buf);
WP(w,querystr_d).maxwidth = 240;
WP(w,querystr_d).buf = _edit_str_buf;
SETBIT(w->click_state, 6);
WP(w,querystr_d).text.caret = false;
WP(w,querystr_d).text.maxlength = lengthof(_edit_str_buf) - 1;
WP(w,querystr_d).text.maxwidth = 240;
WP(w,querystr_d).text.buf = _edit_str_buf;
UpdateTextBufferSize(&WP(w, querystr_d).text);
if (mode == SLD_SAVE_GAME) {
GenerateFileName();
} else if (mode == SLD_SAVE_SCENARIO) {
} else if (mode == SLD_SAVE_SCENARIO)
strcpy(_edit_str_buf, "UNNAMED");
}
// pause is only used in single-player, non-editor mode, non-menu mode. It
// will be unpaused in the WE_DESTROY event handler.