From fb78ca8a62c69a51903416f5a4062a8972c1e98d Mon Sep 17 00:00:00 2001 From: Darkvater Date: Mon, 21 Feb 2005 18:59:54 +0000 Subject: (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(). --- misc_gui.c | 274 ++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 170 insertions(+), 104 deletions(-) (limited to 'misc_gui.c') diff --git a/misc_gui.c b/misc_gui.c index 68d0ad668..2bee2029b 100644 --- a/misc_gui.c +++ b/misc_gui.c @@ -16,11 +16,7 @@ #include "town.h" #include "sound.h" #include "network.h" - -// Windows stuff for Clipboard -#if defined(WIN32) -#include -#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; - - do { - if (*++buf == 0) - break; - (*count)++; - (*width) += _stringwidth_table[(byte)*buf - 32]; - } while (1); + tb->width -= GetCharacterWidth(tb->buf[tb->caretpos]); + memmove(tb->buf + tb->caretpos, tb->buf + tb->caretpos + 1, tb->length - tb->caretpos); + tb->length--; } -int HandleEditBoxKey(Window *w, int wid, WindowEvent *we) +/** + * 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) { - int width,count; - int key = we->keypress.ascii; + if (delmode == WKC_BACKSPACE && tb->caretpos != 0) { + tb->caretpos--; + tb->caretxoffs -= GetCharacterWidth(tb->buf[tb->caretpos]); - we->keypress.cont = false; + DelChar(tb); + return true; + } else if (delmode == WKC_DELETE && tb->caretpos < tb->length) { + DelChar(tb); + return true; + } - 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'; + return false; +} - GlobalUnlock(cbuf); - CloseClipboard(); - InvalidateWidget(w, wid); +/** + * 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; } -#endif - } else { - GetCurrentStringSize(WP(w,querystr_d).buf - 1, &count, &width); + 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; + } - if (we->keypress.keycode == WKC_BACKSPACE) { - if (count != 0) { - WP(w,querystr_d).buf[count-1] = 0; - 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'; + 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) +{ + we->keypress.cont = false; + + 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); + 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 // key wasn't caught we->keypress.cont = true; } @@ -857,42 +916,45 @@ int HandleEditBoxKey(Window *w, int wid, WindowEvent *we) return 0; } -void HandleEditBox(Window *w, int wid) +bool HandleCaret(Textbuf *tb) { - bool b; - /* caret changed? */ - b = !!(_caret_timer & 0x20); - if (b != WP(w,querystr_d).caret) { - WP(w,querystr_d).caret = b; - InvalidateWidget(w, wid); + bool b = !!(_caret_timer & 0x20); + + if (b != tb->caret) { + tb->caret = b; + return true; } + return false; +} + +void HandleEditBox(Window *w, int wid) +{ + 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. -- cgit v1.2.3-54-g00ecf