00001
00002
00003
00004
00005
00006
00007
00008
00009
00012 #include "stdafx.h"
00013 #include <stdarg.h>
00014
00015 #include "textbuf_type.h"
00016 #include "string_func.h"
00017 #include "strings_func.h"
00018 #include "gfx_type.h"
00019 #include "gfx_func.h"
00020 #include "window_func.h"
00021 #include "core/alloc_func.hpp"
00022
00029 bool GetClipboardContents(char *buffer, size_t buff_len);
00030
00031 int _caret_timer;
00032
00033
00040 bool Textbuf::CanDelChar(bool backspace)
00041 {
00042 return backspace ? this->caretpos != 0 : this->caretpos < this->bytes - 1;
00043 }
00044
00051 bool Textbuf::DeleteChar(uint16 keycode)
00052 {
00053 bool word = (keycode & WKC_CTRL) != 0;
00054
00055 keycode &= ~WKC_SPECIAL_KEYS;
00056 if (keycode != WKC_BACKSPACE && keycode != WKC_DELETE) return false;
00057
00058 bool backspace = keycode == WKC_BACKSPACE;
00059
00060 if (!CanDelChar(backspace)) return false;
00061
00062 char *s = this->buf + this->caretpos;
00063 uint16 len = 0;
00064
00065 if (word) {
00066
00067 if (backspace) {
00068
00069 len = this->caretpos - (uint16)this->char_iter->Prev(StringIterator::ITER_WORD);
00070 s -= len;
00071 } else {
00072
00073 len = (uint16)this->char_iter->Next(StringIterator::ITER_WORD) - this->caretpos;
00074 }
00075
00076 for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) {
00077 this->chars--;
00078 }
00079 } else {
00080
00081 if (backspace) {
00082
00083 s = Utf8PrevChar(s);
00084 WChar c;
00085 len = (uint16)Utf8Decode(&c, s);
00086 this->chars--;
00087 } else {
00088
00089 len = (uint16)this->char_iter->Next(StringIterator::ITER_CHARACTER) - this->caretpos;
00090
00091 for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) {
00092 this->chars--;
00093 }
00094 }
00095 }
00096
00097
00098 memmove(s, s + len, this->bytes - (s - this->buf) - len);
00099 this->bytes -= len;
00100
00101 if (backspace) this->caretpos -= len;
00102
00103 this->UpdateStringIter();
00104 this->UpdateWidth();
00105 this->UpdateCaretPosition();
00106 this->UpdateMarkedText();
00107
00108 return true;
00109 }
00110
00114 void Textbuf::DeleteAll()
00115 {
00116 memset(this->buf, 0, this->max_bytes);
00117 this->bytes = this->chars = 1;
00118 this->pixels = this->caretpos = this->caretxoffs = 0;
00119 this->markpos = this->markend = this->markxoffs = this->marklength = 0;
00120 this->UpdateStringIter();
00121 }
00122
00130 bool Textbuf::InsertChar(WChar key)
00131 {
00132 uint16 len = (uint16)Utf8CharLen(key);
00133 if (this->bytes + len <= this->max_bytes && this->chars + 1 <= this->max_chars) {
00134 memmove(this->buf + this->caretpos + len, this->buf + this->caretpos, this->bytes - this->caretpos);
00135 Utf8Encode(this->buf + this->caretpos, key);
00136 this->chars++;
00137 this->bytes += len;
00138 this->caretpos += len;
00139
00140 this->UpdateStringIter();
00141 this->UpdateWidth();
00142 this->UpdateCaretPosition();
00143 this->UpdateMarkedText();
00144 return true;
00145 }
00146 return false;
00147 }
00148
00160 bool Textbuf::InsertString(const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
00161 {
00162 uint16 insertpos = (marked && this->marklength != 0) ? this->markpos : this->caretpos;
00163 if (insert_location != NULL) {
00164 insertpos = insert_location - this->buf;
00165 if (insertpos > this->bytes) return false;
00166
00167 if (replacement_end != NULL) {
00168 this->DeleteText(insertpos, replacement_end - this->buf, str == NULL);
00169 }
00170 } else {
00171 if (marked) this->DiscardMarkedText(str == NULL);
00172 }
00173
00174 if (str == NULL) return false;
00175
00176 uint16 bytes = 0, chars = 0;
00177 WChar c;
00178 for (const char *ptr = str; (c = Utf8Consume(&ptr)) != '\0';) {
00179 if (!IsValidChar(c, this->afilter)) break;
00180
00181 byte len = Utf8CharLen(c);
00182 if (this->bytes + bytes + len > this->max_bytes) break;
00183 if (this->chars + chars + 1 > this->max_chars) break;
00184
00185 bytes += len;
00186 chars++;
00187
00188
00189 if (ptr == caret) this->caretpos = insertpos + bytes;
00190 }
00191
00192 if (bytes == 0) return false;
00193
00194 if (marked) {
00195 this->markpos = insertpos;
00196 this->markend = insertpos + bytes;
00197 }
00198
00199 memmove(this->buf + insertpos + bytes, this->buf + insertpos, this->bytes - insertpos);
00200 memcpy(this->buf + insertpos, str, bytes);
00201
00202 this->bytes += bytes;
00203 this->chars += chars;
00204 if (!marked && caret == NULL) this->caretpos += bytes;
00205 assert(this->bytes <= this->max_bytes);
00206 assert(this->chars <= this->max_chars);
00207 this->buf[this->bytes - 1] = '\0';
00208
00209 this->UpdateStringIter();
00210 this->UpdateWidth();
00211 this->UpdateCaretPosition();
00212 this->UpdateMarkedText();
00213
00214 return true;
00215 }
00216
00223 bool Textbuf::InsertClipboard()
00224 {
00225 char utf8_buf[512];
00226
00227 if (!GetClipboardContents(utf8_buf, lengthof(utf8_buf))) return false;
00228
00229 return this->InsertString(utf8_buf, false);
00230 }
00231
00238 void Textbuf::DeleteText(uint16 from, uint16 to, bool update)
00239 {
00240 uint c = 0;
00241 const char *s = this->buf + from;
00242 while (s < this->buf + to) {
00243 Utf8Consume(&s);
00244 c++;
00245 }
00246
00247
00248 memmove(this->buf + from, this->buf + to, this->bytes - to);
00249 this->bytes -= to - from;
00250 this->chars -= c;
00251
00252
00253 if (this->caretpos > from) {
00254 if (this->caretpos <= to) {
00255 this->caretpos = from;
00256 } else {
00257 this->caretpos -= to - from;
00258 }
00259 }
00260
00261 if (update) {
00262 this->UpdateStringIter();
00263 this->UpdateCaretPosition();
00264 this->UpdateMarkedText();
00265 }
00266 }
00267
00272 void Textbuf::DiscardMarkedText(bool update)
00273 {
00274 if (this->markend == 0) return;
00275
00276 this->DeleteText(this->markpos, this->markend, update);
00277 this->markpos = this->markend = this->markxoffs = this->marklength = 0;
00278 }
00279
00281 void Textbuf::UpdateStringIter()
00282 {
00283 this->char_iter->SetString(this->buf);
00284 size_t pos = this->char_iter->SetCurPosition(this->caretpos);
00285 this->caretpos = pos == StringIterator::END ? 0 : (uint16)pos;
00286 }
00287
00289 void Textbuf::UpdateWidth()
00290 {
00291 this->pixels = GetStringBoundingBox(this->buf, FS_NORMAL).width;
00292 }
00293
00295 void Textbuf::UpdateCaretPosition()
00296 {
00297 this->caretxoffs = this->chars > 1 ? GetCharPosInString(this->buf, this->buf + this->caretpos, FS_NORMAL).x : 0;
00298 }
00299
00301 void Textbuf::UpdateMarkedText()
00302 {
00303 if (this->markend != 0) {
00304 this->markxoffs = GetCharPosInString(this->buf, this->buf + this->markpos, FS_NORMAL).x;
00305 this->marklength = GetCharPosInString(this->buf, this->buf + this->markend, FS_NORMAL).x - this->markxoffs;
00306 } else {
00307 this->markxoffs = this->marklength = 0;
00308 }
00309 }
00310
00317 bool Textbuf::MovePos(uint16 keycode)
00318 {
00319 switch (keycode) {
00320 case WKC_LEFT:
00321 case WKC_CTRL | WKC_LEFT: {
00322 if (this->caretpos == 0) break;
00323
00324 size_t pos = this->char_iter->Prev(keycode & WKC_CTRL ? StringIterator::ITER_WORD : StringIterator::ITER_CHARACTER);
00325 if (pos == StringIterator::END) return true;
00326
00327 this->caretpos = (uint16)pos;
00328 this->UpdateCaretPosition();
00329 return true;
00330 }
00331
00332 case WKC_RIGHT:
00333 case WKC_CTRL | WKC_RIGHT: {
00334 if (this->caretpos >= this->bytes - 1) break;
00335
00336 size_t pos = this->char_iter->Next(keycode & WKC_CTRL ? StringIterator::ITER_WORD : StringIterator::ITER_CHARACTER);
00337 if (pos == StringIterator::END) return true;
00338
00339 this->caretpos = (uint16)pos;
00340 this->UpdateCaretPosition();
00341 return true;
00342 }
00343
00344 case WKC_HOME:
00345 this->caretpos = 0;
00346 this->char_iter->SetCurPosition(this->caretpos);
00347 this->UpdateCaretPosition();
00348 return true;
00349
00350 case WKC_END:
00351 this->caretpos = this->bytes - 1;
00352 this->char_iter->SetCurPosition(this->caretpos);
00353 this->UpdateCaretPosition();
00354 return true;
00355
00356 default:
00357 break;
00358 }
00359
00360 return false;
00361 }
00362
00370 Textbuf::Textbuf(uint16 max_bytes, uint16 max_chars)
00371 : buf(MallocT<char>(max_bytes))
00372 {
00373 assert(max_bytes != 0);
00374 assert(max_chars != 0);
00375
00376 this->char_iter = StringIterator::Create();
00377
00378 this->afilter = CS_ALPHANUMERAL;
00379 this->max_bytes = max_bytes;
00380 this->max_chars = max_chars == UINT16_MAX ? max_bytes : max_chars;
00381 this->caret = true;
00382 this->DeleteAll();
00383 }
00384
00385 Textbuf::~Textbuf()
00386 {
00387 delete this->char_iter;
00388 free(this->buf);
00389 }
00390
00395 void Textbuf::Assign(StringID string)
00396 {
00397 GetString(this->buf, string, &this->buf[this->max_bytes - 1]);
00398 this->UpdateSize();
00399 }
00400
00405 void Textbuf::Assign(const char *text)
00406 {
00407 ttd_strlcpy(this->buf, text, this->max_bytes);
00408 this->UpdateSize();
00409 }
00410
00414 void Textbuf::Print(const char *format, ...)
00415 {
00416 va_list va;
00417 va_start(va, format);
00418 vsnprintf(this->buf, this->max_bytes, format, va);
00419 va_end(va);
00420 this->UpdateSize();
00421 }
00422
00423
00429 void Textbuf::UpdateSize()
00430 {
00431 const char *buf = this->buf;
00432
00433 this->chars = this->bytes = 1;
00434
00435 WChar c;
00436 while ((c = Utf8Consume(&buf)) != '\0') {
00437 this->bytes += Utf8CharLen(c);
00438 this->chars++;
00439 }
00440 assert(this->bytes <= this->max_bytes);
00441 assert(this->chars <= this->max_chars);
00442
00443 this->caretpos = this->bytes - 1;
00444 this->UpdateStringIter();
00445 this->UpdateWidth();
00446 this->UpdateMarkedText();
00447
00448 this->UpdateCaretPosition();
00449 }
00450
00455 bool Textbuf::HandleCaret()
00456 {
00457
00458 bool b = !!(_caret_timer & 0x20);
00459
00460 if (b != this->caret) {
00461 this->caret = b;
00462 return true;
00463 }
00464 return false;
00465 }
00466
00467 HandleKeyPressResult Textbuf::HandleKeyPress(WChar key, uint16 keycode)
00468 {
00469 bool edited = false;
00470
00471 switch (keycode) {
00472 case WKC_ESC: return HKPR_CANCEL;
00473
00474 case WKC_RETURN: case WKC_NUM_ENTER: return HKPR_CONFIRM;
00475
00476 #ifdef WITH_COCOA
00477 case (WKC_META | 'V'):
00478 #endif
00479 case (WKC_CTRL | 'V'):
00480 edited = this->InsertClipboard();
00481 break;
00482
00483 #ifdef WITH_COCOA
00484 case (WKC_META | 'U'):
00485 #endif
00486 case (WKC_CTRL | 'U'):
00487 this->DeleteAll();
00488 edited = true;
00489 break;
00490
00491 case WKC_BACKSPACE: case WKC_DELETE:
00492 case WKC_CTRL | WKC_BACKSPACE: case WKC_CTRL | WKC_DELETE:
00493 edited = this->DeleteChar(keycode);
00494 break;
00495
00496 case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
00497 case WKC_CTRL | WKC_LEFT: case WKC_CTRL | WKC_RIGHT:
00498 this->MovePos(keycode);
00499 break;
00500
00501 default:
00502 if (IsValidChar(key, this->afilter)) {
00503 edited = this->InsertChar(key);
00504 } else {
00505 return HKPR_NOT_HANDLED;
00506 }
00507 break;
00508 }
00509
00510 return edited ? HKPR_EDITING : HKPR_CURSOR;
00511 }