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 }