# textbuf.cpp

Go to the documentation of this file.

    00001 /* $Id: textbuf.cpp 25708 2013-08-10 12:47:11Z fonsinchen $ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * OpenTTD 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, version 2.
    00006  * OpenTTD 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.
    00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
    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     /* Delete a complete word. */
    00067     if (backspace) {
    00068       /* Delete whitespace and word in front of the caret. */
    00069       len = this->caretpos - (uint16)this->char_iter->Prev(StringIterator::ITER_WORD);
    00070       s -= len;
    00071     } else {
    00072       /* Delete word and following whitespace following the caret. */
    00073       len = (uint16)this->char_iter->Next(StringIterator::ITER_WORD) - this->caretpos;
    00074     }
    00075     /* Update character count. */
    00076     for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) {
    00077       this->chars--;
    00078     }
    00079   } else {
    00080     /* Delete a single character. */
    00081     if (backspace) {
    00082       /* Delete the last code point in front of the caret. */
    00083       s = Utf8PrevChar(s);
    00084       WChar c;
    00085       len = (uint16)Utf8Decode(&c, s);
    00086       this->chars--;
    00087     } else {
    00088       /* Delete the complete character following the caret. */
    00089       len = (uint16)this->char_iter->Next(StringIterator::ITER_CHARACTER) - this->caretpos;
    00090       /* Update character count. */
    00091       for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) {
    00092         this->chars--;
    00093       }
    00094     }
    00095   }
    00096
    00097   /* Move the remaining characters over the marker */
    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     /* Move caret if needed. */
    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'; // terminating zero
    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   /* Strip marked characters from buffer. */
    00248   memmove(this->buf + from, this->buf + to, this->bytes - to);
    00249   this->bytes -= to - from;
    00250   this->chars -= c;
    00251
    00252   /* Fixup caret if needed. */
    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; // terminating zero
    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   /* caret changed? */
    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 }